From b2ed422d143ae53c00bde26eb7f588f5f7410b2c Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Tue, 5 Dec 2023 17:21:43 -0500 Subject: [PATCH 01/11] Hierarchical dependency injection framework Adapted from this experiment: https://stackblitz.com/edit/vitejs-vite-6tenu8?file=src%2Fapp.ts --- framework/src/index.ts | 3 +- framework/src/lib/decorators/index.ts | 1 + framework/src/lib/decorators/internal.ts | 65 ++++++++++ framework/src/lib/decorators/on.ts | 28 ++--- framework/src/lib/di/decorators.ts | 145 +++++++++++++++++++++++ framework/src/lib/di/events.ts | 73 ++++++++++++ framework/src/lib/di/index.ts | 3 + framework/src/lib/di/types.ts | 21 ++++ utility/src/lib/types.ts | 16 +++ 9 files changed, 334 insertions(+), 21 deletions(-) create mode 100644 framework/src/lib/decorators/index.ts create mode 100644 framework/src/lib/decorators/internal.ts create mode 100644 framework/src/lib/di/decorators.ts create mode 100644 framework/src/lib/di/events.ts create mode 100644 framework/src/lib/di/index.ts create mode 100644 framework/src/lib/di/types.ts diff --git a/framework/src/index.ts b/framework/src/index.ts index 49b1fa11..6b919477 100644 --- a/framework/src/index.ts +++ b/framework/src/index.ts @@ -1,2 +1,3 @@ -export * from "./lib/decorators/on"; +export * from "./lib/decorators"; +export * from "./lib/di"; export { default as spread } from "./lib/directives/spread"; diff --git a/framework/src/lib/decorators/index.ts b/framework/src/lib/decorators/index.ts new file mode 100644 index 00000000..66c74fd3 --- /dev/null +++ b/framework/src/lib/decorators/index.ts @@ -0,0 +1 @@ +export * from "./on"; diff --git a/framework/src/lib/decorators/internal.ts b/framework/src/lib/decorators/internal.ts new file mode 100644 index 00000000..026fe5ee --- /dev/null +++ b/framework/src/lib/decorators/internal.ts @@ -0,0 +1,65 @@ +import { Method } from "@storyteller/utility"; +import { LitElement } from "lit"; + +/** + * Modifies the prototype function at `proto[key]` by adding an invocation of + * `callback` to the end of the method body. + */ +export function appendRoutine< + E extends LitElement, + M extends Method, +>(proto: E, key: string | number | symbol, callback: M): void { + const original = getMethodDescriptor(proto, key)?.value; + if (!original || typeof original !== "function") + return; + + Object.defineProperty(proto, key, { + value(this: E): void { + original.call(this); + callback.call(this); + }, + configurable: true, + }); +} + +/** + * Modifies the prototype function at `proto[key]` by adding an invocation of + * `callback` to the start of the method body. + */ +export function prependRoutine< + E extends LitElement, + M extends Method, +>(proto: E, key: string | number | symbol, callback: M): void { + const original = getMethodDescriptor(proto, key)?.value; + if (!original || typeof original !== "function") + return; + + Object.defineProperty(proto, key, { + value(this: E): void { + callback.call(this); + original.call(this); + }, + configurable: true, + }); +} + +/** + * Looks for a member in the prototype chain named `key`, starting at `proto` + * and recursing up the inheritance tree. If it reaches `HTMLElement` without + * finding a match, it stops and returns `undefined` -- otherwise, it returns + * the matching property descriptor. + */ +export function getMethodDescriptor(proto: E, key: string | number | symbol) + : TypedPropertyDescriptor> | undefined +{ + let result = Object.getOwnPropertyDescriptor(proto, key); + while (!result) { + proto = Object.getPrototypeOf(proto); + if (!proto || proto === HTMLElement.prototype) + break; + + result = Object.getOwnPropertyDescriptor(proto, key); + } + + return result; +} diff --git a/framework/src/lib/decorators/on.ts b/framework/src/lib/decorators/on.ts index e15ad57c..dbd4f5bd 100644 --- a/framework/src/lib/decorators/on.ts +++ b/framework/src/lib/decorators/on.ts @@ -1,5 +1,6 @@ import { match } from "@storyteller/utility"; import type { LitElement } from "lit"; +import { getMethodDescriptor, prependRoutine } from "./internal"; /** * Adds the decorated method as an event listener for the given event name, and @@ -21,29 +22,16 @@ export function on(eventSelector: string) { }); return (proto: T, propName: keyof T, desc: PropertyDescriptor) => { - const handler = desc?.value ?? proto[propName] ?? (() => {}); + const handler = getMethodDescriptor(proto, propName)?.value ?? (() => {}); - const connectedCb = proto.connectedCallback; - const disconnectedCb = proto.disconnectedCallback; - - Object.defineProperty(proto, "connectedCallback", { - value: function (this: HTMLElement) { - target ??= this; - target.addEventListener(eventName, handler.bind(this)); - connectedCb.call(this); - }, - configurable: true, - writable: true, + prependRoutine(proto, "connectedCallback", function (this: T) { + target ??= this; + target.addEventListener(eventName, handler.bind(this)); }); - Object.defineProperty(proto, "disconnectedCallback", { - value: function (this: HTMLElement) { - target ??= this; - target.removeEventListener(eventName, handler.bind(this)); - disconnectedCb.call(this); - }, - configurable: true, - writable: true, + prependRoutine(proto, "disconnectedCallback", function (this: T) { + target ??= this; + target.removeEventListener(eventName, handler.bind(this)); }); return desc; diff --git a/framework/src/lib/di/decorators.ts b/framework/src/lib/di/decorators.ts new file mode 100644 index 00000000..84ed7dd6 --- /dev/null +++ b/framework/src/lib/di/decorators.ts @@ -0,0 +1,145 @@ +import { Ctor, Method } from "@storyteller/utility"; +import { LitElement } from "lit"; + +import { appendRoutine, prependRoutine } from "../decorators/internal"; +import { DIEvent, DependencyProvision, InjectionRequest, ProviderRemoval } from "./events"; +import { Token } from "./types"; + +const $injector = Symbol("injector"); +const $$injector = Symbol("#injector"); + +export type ProviderOptions + = Token + | { + token: Token, + provide(this: E): T, + }; + +export function provide(options: ProviderOptions) { + type Decorated = E & { + [$$injector]?: Map, any> | undefined; + readonly [$injector]: Map, any>; + } + + return (Target: Ctor) => { + const proto = Target.prototype; + 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, + enumerable: true, + }); + } + + function fulfillInjectionRequest(this: Decorated, event: InjectionRequest): void { + if (this[$injector].has(event.detail.token)) { + event.detail.value = this[$injector].get(event.detail.token); + event.stopImmediatePropagation(); + } + } + + prependRoutine(proto, "connectedCallback", function (this: Decorated): void { + const { token, value } = "token" in options + ? { token: options.token, value: options.provide.call(this) } + : { token: options, value: this as T }; + + 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); + } + }); + + appendRoutine(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)); + }); + } +} + +export function inject(token: Token) { + return (proto: E, key: K) => { + const $field = Symbol(String(key)); + const initial = proto[key]; + + type Decorated = E & { + [$field]?: E[K] | undefined; + } + + assertType(proto); + + Object.defineProperty(proto, key, { + get(this: Decorated): E[K] { + return this[$field] ??= initial; + }, + set(this: Decorated, value: E[K]): void { + this[$field] = value; + }, + configurable: true, + enumerable: true, + }); + + prependRoutine(proto, "connectedCallback", function (this: E): void { + const event = new InjectionRequest(token); + this.dispatchEvent(event); + + if (event.detail.value != null) { + this[key] = event.detail.value as E[K]; + } + }); + } +} + +export function queryProviders(token: Token) { + return (proto: E, key: K) => { + function onProvided(this: E, event: DependencyProvision): void { + const array = this[key]; + assertType(array); + + if (event.detail.token === token) + this[key] = array.concat(event.detail.value) as E[K]; + } + + function onRemoved(this: E, event: ProviderRemoval): void { + const array = this[key]; + assertType(array); + + if (event.detail.token === token) + this[key] = array.filter(el => el !== event.detail.value) as E[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, + ); + }); + + 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, + ); + }); + } +} + +function assertType(value: unknown): asserts value is T {} diff --git a/framework/src/lib/di/events.ts b/framework/src/lib/di/events.ts new file mode 100644 index 00000000..629d5c82 --- /dev/null +++ b/framework/src/lib/di/events.ts @@ -0,0 +1,73 @@ +import { WithOpt } from "@storyteller/utility"; +import { Provider, Token } from "./types"; + +export enum DIEvent { + InjectionRequest = "di-inject", + DependencyProvision = "di-provide", + ProviderRemoval = "di-remove", +} + +type PendingProvider = WithOpt, "value">; + +/** + * An event that can be dispatched to inject a dependency provided by an + * ancestor node. + * + * Providers should listen for this event. If they provide a match for the + * event's `detail.token`, they should populate its `detail.value` property and + * call `stopPropagation()` to prevent further bubbling. + */ +export class InjectionRequest extends CustomEvent> { + declare readonly type: DIEvent.InjectionRequest; + + constructor (token: Token) { + super(DIEvent.InjectionRequest, { + bubbles: true, + cancelable: true, + composed: true, + detail: { token }, + }); + } +} + +/** + * An event that can be dispatched to inform ancestors that this node provides a + * dependency. + */ +export class DependencyProvision extends CustomEvent> { + declare readonly type: DIEvent.DependencyProvision; + + constructor (token: Token, value: T) { + super(DIEvent.DependencyProvision, { + bubbles: true, + cancelable: true, + composed: true, + detail: { token, value }, + }); + } +} + +/** + * An event that can be dispatched to inform ancestors when a node providing a + * dependency is removed from the tree. + */ +export class ProviderRemoval extends CustomEvent> { + declare readonly type: DIEvent.ProviderRemoval; + + constructor (token: Token, value: T) { + super(DIEvent.ProviderRemoval, { + bubbles: true, + cancelable: true, + composed: true, + detail: { token, value }, + }); + } +} + +declare global { + interface HTMLElementEventMap { + [DIEvent.InjectionRequest]: InjectionRequest; + [DIEvent.DependencyProvision]: DependencyProvision; + [DIEvent.ProviderRemoval]: ProviderRemoval; + } +} diff --git a/framework/src/lib/di/index.ts b/framework/src/lib/di/index.ts new file mode 100644 index 00000000..ce72d0c6 --- /dev/null +++ b/framework/src/lib/di/index.ts @@ -0,0 +1,3 @@ +export * from "./decorators"; +export * from "./events"; +export * from "./types"; diff --git a/framework/src/lib/di/types.ts b/framework/src/lib/di/types.ts new file mode 100644 index 00000000..9eb09e57 --- /dev/null +++ b/framework/src/lib/di/types.ts @@ -0,0 +1,21 @@ +import { AbstractCtor, Ctor } from "@storyteller/utility"; + +export class UniqueToken { + get id() { return this.#id; } + readonly #id: symbol; + + constructor (name: string) { + this.#id = Symbol(name); + } +} + +export type Token + = UniqueToken + | Ctor + | AbstractCtor + ; + +export interface Provider { + token: Token; + value: T; +} diff --git a/utility/src/lib/types.ts b/utility/src/lib/types.ts index 34b592bc..c2182aaf 100644 --- a/utility/src/lib/types.ts +++ b/utility/src/lib/types.ts @@ -1,7 +1,23 @@ +export interface Ctor { + new (...args: Args): T; + prototype: T; +} + +export type AbstractCtor + = (abstract new (...args: Args) => T) + & { prototype: T } + ; + export interface Fn { (...args: Args): R; } +export interface Method { + (this: T, ...args: Args): R; +} + +export type Opt = T | null | undefined; + export type WithOpt = Omit & Partial> From 07456eabc7e94f7b5676947505715001d717927f Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Tue, 5 Dec 2023 17:25:06 -0500 Subject: [PATCH 02/11] `CanvasProvider` to juggle a single Bevy canvas --- studio-web/package.json | 1 + .../src/lib/canvas-provider.element.scss | 3 + studio-web/src/lib/canvas-provider.element.ts | 94 +++++++++++++++++++ studio-web/src/lib/viewer.element.scss | 13 +++ studio-web/src/lib/viewer.element.ts | 49 ++++++++-- tsconfig.base.json | 1 + 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 studio-web/src/lib/canvas-provider.element.scss create mode 100644 studio-web/src/lib/canvas-provider.element.ts diff --git a/studio-web/package.json b/studio-web/package.json index 1602a831..c9dd07d5 100644 --- a/studio-web/package.json +++ b/studio-web/package.json @@ -6,6 +6,7 @@ "module": "./index.mjs", "typings": "./index.d.ts", "exports": { + "./canvas-provider": "./canvas-provider.element.js", "./viewer": "./viewer.element.js" } } diff --git a/studio-web/src/lib/canvas-provider.element.scss b/studio-web/src/lib/canvas-provider.element.scss new file mode 100644 index 00000000..7e928ca8 --- /dev/null +++ b/studio-web/src/lib/canvas-provider.element.scss @@ -0,0 +1,3 @@ +::slotted([slot="canvas"]) { + display: none; +} diff --git a/studio-web/src/lib/canvas-provider.element.ts b/studio-web/src/lib/canvas-provider.element.ts new file mode 100644 index 00000000..9a56fedb --- /dev/null +++ b/studio-web/src/lib/canvas-provider.element.ts @@ -0,0 +1,94 @@ +import { Token, UniqueToken, provide } from "@storyteller/framework"; +import { LitElement, html, unsafeCSS } from "lit"; +import { customElement } from "lit/decorators.js"; + +import styles from "./canvas-provider.element.scss?inline"; + +export interface CanvasProvider { + readonly host: HTMLElement; + readonly canvasId: string; + readonly canvas: HTMLCanvasElement | undefined; + + /** + * Remove the canvas element from the provider host and return it. This can + * be used to temporarily move the canvas element to a different location in + * the DOM tree (e.g., for displaying in a modal dialog). + * + * `returnCanvas` must be called to restore the canvas element to its + * original location before the canvas's new parent element is removed from + * the document. If the canvas element is entirely absent from the document + * at any point after initialization (i.e., if + * `document.getElementById(canvasId)` returns `null`), the + * `@storyteller/studio` app will panic. + */ + takeCanvas(): HTMLCanvasElement | undefined; + + /** + * Return the canvas element to the provider after removing it via + * `takeCanvas`. + * + * When `takeCanvas` is used to move the canvas element to a new location, + * this method must be called before the canvas's new parent is removed from + * the DOM tree, to ensure that the canvas element is always accessible to + * `@storyteller/studio`. + */ + returnCanvas(canvas: HTMLCanvasElement): void; +} + +export const CANVAS_PROVIDER: Token = new UniqueToken("CanvasProvider"); + +@customElement("sts-canvas-provider") +@provide(CANVAS_PROVIDER) +export class CanvasProviderElement + extends LitElement + implements CanvasProvider +{ + static override styles = unsafeCSS(styles); + + get host() { return this; } + get canvasId() { return this.#canvasId; } + get canvas(): HTMLCanvasElement | undefined { return this.#canvas; } + + #canvasId = "studio_canvas"; + #canvas?: HTMLCanvasElement; + + takeCanvas(): HTMLCanvasElement | undefined { + if (!this.#canvas) return; + + const canvas = this.removeChild(this.#canvas); + this.#canvas = undefined; + + return canvas; + } + + returnCanvas(canvas: HTMLCanvasElement) { + this.appendChild(canvas); + this.#canvas = canvas; + } + + override connectedCallback(): void { + this.#canvas ??= this.#appendLightDomCanvas(); + super.connectedCallback(); + } + + protected override render = () => html` + + + `; + + #appendLightDomCanvas(): HTMLCanvasElement { + const canvas = document.createElement("canvas"); + canvas.id = this.#canvasId; + canvas.slot = "canvas"; + + this.appendChild(canvas); + + return canvas; + } +} + +declare global { + export interface HTMLElementTagNameMap { + "sts-canvas-provider": CanvasProviderElement; + } +} diff --git a/studio-web/src/lib/viewer.element.scss b/studio-web/src/lib/viewer.element.scss index e69de29b..e9de20f9 100644 --- a/studio-web/src/lib/viewer.element.scss +++ b/studio-web/src/lib/viewer.element.scss @@ -0,0 +1,13 @@ +:host { + display: block; + position: relative; + width: 100%; + height: 100%; +} + +::slotted([slot="canvas"]) { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} diff --git a/studio-web/src/lib/viewer.element.ts b/studio-web/src/lib/viewer.element.ts index e83ea3b5..5b3652c1 100644 --- a/studio-web/src/lib/viewer.element.ts +++ b/studio-web/src/lib/viewer.element.ts @@ -1,9 +1,12 @@ import * as studio from "@storyteller/studio"; +import { ViewportSize } from "@storyteller/studio"; +import { inject } from "@storyteller/framework"; import { LitElement, PropertyValues, html, unsafeCSS } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { CANVAS_PROVIDER, CanvasProvider } from "./canvas-provider.element"; + import styles from "./viewer.element.scss?inline"; -import { ViewportSize } from "@storyteller/studio"; @customElement("sts-viewer") export class ViewerElement extends LitElement { @@ -12,8 +15,29 @@ export class ViewerElement extends LitElement { @property() objectId?: string; @property() skyboxId?: string; - protected override createRenderRoot(): Element | ShadowRoot { - return this; + @inject(CANVAS_PROVIDER) + _canvasProvider!: CanvasProvider; + + #canvas?: HTMLCanvasElement; + #resizeObserver?: ResizeObserver; + + override connectedCallback(): void { + super.connectedCallback(); + + this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this)); + this.#resizeObserver.observe(this); + + if ((this.#canvas = this._canvasProvider.takeCanvas())) + this.appendChild(this.#canvas); + } + + override disconnectedCallback(): void { + if (this.#canvas) + this._canvasProvider.returnCanvas(this.removeChild(this.#canvas)); + + this.#resizeObserver?.disconnect(); + + super.disconnectedCallback(); } protected override updated(changes: PropertyValues): void { @@ -28,17 +52,26 @@ export class ViewerElement extends LitElement { } } + protected override render = () => html` + + `; + + #onResize(): void { + const { width, height } = this.getBoundingClientRect(); + studio.resize(new ViewportSize(width, height)); + } + #startViewer(): void { + if (!this.#canvas) return; if (!this.objectId) return; if (!this.skyboxId) return; - const width = window.innerWidth; - const height = window.innerHeight; + const { width, height } = this.getBoundingClientRect(); try { studio.startViewer( this.objectId, this.skyboxId, - "#studio_canvas", + `#${this._canvasProvider.canvasId}`, "Storyteller Studio", new ViewportSize(width, height), ); @@ -47,10 +80,6 @@ export class ViewerElement extends LitElement { throw err; } } - - protected override render = () => html` - - `; } declare global { diff --git a/tsconfig.base.json b/tsconfig.base.json index ffb57268..f6337bf6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -18,6 +18,7 @@ "@storyteller/framework": ["framework/src/index.ts"], "@storyteller/studio": ["dist/studio"], "@storyteller/studio-web": ["studio-web/src/index.ts"], + "@storyteller/studio-web/canvas-provider": ["studio-web/src/lib/canvas-provider.element.ts"], "@storyteller/studio-web/viewer": ["studio-web/src/lib/viewer.element.ts"], "@storyteller/utility": ["utility/src/index.ts"] } From 38a414164184addfe34a2fa1a1658bca664f904d Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Tue, 5 Dec 2023 17:27:20 -0500 Subject: [PATCH 03/11] Update `react-frontend` example --- react-frontend/src/app/app.module.scss | 84 ++++++++++-- react-frontend/src/app/app.tsx | 123 +++++++++--------- .../src/app/studio-canvas-provider.tsx | 15 +++ react-frontend/src/app/studio-viewer.tsx | 6 +- react-frontend/src/styles.scss | 1 - react-frontend/src/types.d.ts | 5 + studio-frontend/src/app/app.element.scss | 8 +- 7 files changed, 172 insertions(+), 70 deletions(-) create mode 100644 react-frontend/src/app/studio-canvas-provider.tsx diff --git a/react-frontend/src/app/app.module.scss b/react-frontend/src/app/app.module.scss index d53b5376..bdda68fa 100644 --- a/react-frontend/src/app/app.module.scss +++ b/react-frontend/src/app/app.module.scss @@ -1,5 +1,58 @@ .root { position: relative; + padding: 64px; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 32px; +} + +.thumbnail { + flex: 0 0 352px; + width: 352px; + height: 352px; + background: #444; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + &:hover { + background: #666; + } +} + +.modalOverlay { + position: fixed; + inset: 0; + background: #2222227F; + backdrop-filter: blur(12px); +} + +.modal { + position: fixed; + width: 1280px; + height: 720px; + top: calc(50vh - 360px); + left: calc(50vw - 640px); + border-radius: 16px; + overflow: hidden; + box-shadow: + 0 0 16px rgba(0,0,0,0.17), + 0 16px 48px rgba(0,0,0,0.3); +} + +.loading { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: #222; + pointer-events: none; + transition: opacity 66.666ms linear; } .controls { @@ -34,13 +87,28 @@ } } -.loading { +.modalClose { position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: #222; - pointer-events: none; - transition: opacity 66.666ms linear; + top: 16px; + right: 16px; + width: 48px; + height: 48px; + line-height: 48px; + border-radius: 50%; + appearance: none; + border: none; + padding: 0; + margin: 0; + background: #2222227F; + backdrop-filter: blur(16px); + font: unset { + family: inherit; + weight: 400; + size: 40px; + } + cursor: pointer; + + &:hover { + background: #222222AA; + } } diff --git a/react-frontend/src/app/app.tsx b/react-frontend/src/app/app.tsx index c0cb4710..69408d92 100644 --- a/react-frontend/src/app/app.tsx +++ b/react-frontend/src/app/app.tsx @@ -2,6 +2,7 @@ import { type SceneStateEvent, SceneState } from "@storyteller/studio"; import { FormEvent, useState } from "react"; import StudioViewer from "./studio-viewer"; +import StudioCanvasProvider from "./studio-canvas-provider"; import style from "./app.module.scss"; @@ -79,14 +80,10 @@ const SKYBOXES = [ ]; export default () => { - const [objectId, setObjectId] = useState("desk"); - const [skyboxId, setSkyboxId] = useState("test_scene"); + const [objectId, setObjectId] = useState(null); + const [skyboxId, setSkyboxId] = useState("meadow_4k"); const [loading, setLoading] = useState(true); - const onObjectSelect = (event: FormEvent) => { - setObjectId((event.target as HTMLSelectElement).value) - } - const onSkyboxSelect = (event: FormEvent) => { setSkyboxId((event.target as HTMLSelectElement).value) } @@ -96,62 +93,72 @@ export default () => { } return ( -
- - -
-

Loading...

+ +
+ {OBJECTS.map(({ id, label }) => ( +
{ + setLoading(true); + setObjectId(id); + }} + > +

{label}

+
+ ))}
-
- - +
+ +
-
+ × + + + } + ); } diff --git a/react-frontend/src/app/studio-canvas-provider.tsx b/react-frontend/src/app/studio-canvas-provider.tsx new file mode 100644 index 00000000..290c762b --- /dev/null +++ b/react-frontend/src/app/studio-canvas-provider.tsx @@ -0,0 +1,15 @@ +import { CanvasProviderElement } from "@storyteller/studio-web/canvas-provider"; +import "@storyteller/studio-web/canvas-provider"; +import React, { ReactNode, forwardRef } from "react"; + +export interface Props extends React.HTMLProps { + children?: ReactNode | ReactNode[]; +} + +export default forwardRef( + ({ children, ...props }, ref) => ( + + {children} + + ) +); diff --git a/react-frontend/src/app/studio-viewer.tsx b/react-frontend/src/app/studio-viewer.tsx index e92a328b..a5a2ea50 100644 --- a/react-frontend/src/app/studio-viewer.tsx +++ b/react-frontend/src/app/studio-viewer.tsx @@ -1,9 +1,9 @@ import type { SceneStateEvent } from "@storyteller/studio"; import { ViewerElement } from "@storyteller/studio-web/viewer"; import "@storyteller/studio-web/viewer"; -import { MutableRefObject, forwardRef, useEffect, useRef } from "react"; +import React, { MutableRefObject, forwardRef, useEffect, useRef } from "react"; -export interface Props { +export interface Props extends React.HTMLProps { objectId: string; skyboxId: string; onSceneStateChange?(event: SceneStateEvent): void; @@ -13,6 +13,7 @@ export default forwardRef(({ objectId, skyboxId, onSceneStateChange, + ...props }, ref) => { const viewerRef = useRef(null); @@ -38,6 +39,7 @@ export default forwardRef(({ return ( , ViewerElement, >; + "sts-canvas-provider": React.DetailedHTMLProps< + React.HTMLAttributes, + CanvasProviderElement, + > } } } diff --git a/studio-frontend/src/app/app.element.scss b/studio-frontend/src/app/app.element.scss index d6c1b7f7..7d603207 100644 --- a/studio-frontend/src/app/app.element.scss +++ b/studio-frontend/src/app/app.element.scss @@ -32,13 +32,14 @@ font-size: 16px; padding: 10px 12px; pointer-events: auto; + outline: 0 solid transparent; // TODO: Copypasta background: #0006; color: #FFFC; backdrop-filter: blur(16px); transition: { - property: background, color; + property: background, color, outline-width, outline-color; duration: 0.1s; timing-function: linear; } @@ -62,6 +63,11 @@ color: #FFF; } + &:has(:focus-visible) { + z-index: 1; + outline: 4px solid #2F83FFBF; + } + &:has(:checked) { background: #7A2C34E3; color: #FFF; From 2131eec8913d134feea33d16b497025cef92143f Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 00:36:27 -0500 Subject: [PATCH 04/11] Fix `studio-web` build errors --- studio-web/src/index.ts | 1 + studio-web/vite.config.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/studio-web/src/index.ts b/studio-web/src/index.ts index 65788a51..d51e81e4 100644 --- a/studio-web/src/index.ts +++ b/studio-web/src/index.ts @@ -1 +1,2 @@ +export * from "./lib/canvas-provider.element"; export * from "./lib/viewer.element"; diff --git a/studio-web/vite.config.ts b/studio-web/vite.config.ts index 1e2e7f9a..c983e7b8 100644 --- a/studio-web/vite.config.ts +++ b/studio-web/vite.config.ts @@ -1,14 +1,18 @@ /// +import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin"; import { defineConfig } from "vite"; import dts from "vite-plugin-dts"; +import wasm from "vite-plugin-wasm"; +import topLevelAwait from "vite-plugin-top-level-await"; import * as path from "path"; -import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin"; export default defineConfig({ cacheDir: "../node_modules/.vite/studio-web", plugins: [ nxViteTsPaths(), + wasm(), + topLevelAwait(), dts({ entryRoot: "src", tsConfigFilePath: path.join(__dirname, "tsconfig.lib.json"), @@ -31,7 +35,7 @@ export default defineConfig({ fileName: "index", // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ["es", "cjs"], + formats: ["es"], }, rollupOptions: { // External packages that should not be bundled into your library. From ffff83f7caee47e0f4b8f31daa7cb93b248ae80a Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 01:00:49 -0500 Subject: [PATCH 05/11] Avoid bundling dependencies; Build CommonJS target --- studio-web/package.json | 12 ++++++------ studio-web/project.json | 6 ++++++ studio-web/vite.config.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/studio-web/package.json b/studio-web/package.json index c9dd07d5..2b55d35d 100644 --- a/studio-web/package.json +++ b/studio-web/package.json @@ -1,12 +1,12 @@ { "name": "@storyteller/studio-web", "version": "0.0.1", - "dependencies": {}, + "dependencies": { + "@storyteller/framework": "0.0.1", + "@storyteller/studio": "0.0.1", + "lit": "^2.8.0" + }, "main": "./index.js", "module": "./index.mjs", - "typings": "./index.d.ts", - "exports": { - "./canvas-provider": "./canvas-provider.element.js", - "./viewer": "./viewer.element.js" - } + "typings": "./index.d.ts" } diff --git a/studio-web/project.json b/studio-web/project.json index 6f29b37b..cc6f2c8b 100644 --- a/studio-web/project.json +++ b/studio-web/project.json @@ -7,6 +7,12 @@ "build": { "executor": "@nx/vite:build", "outputs": ["{options.outputPath}"], + "dependsOn": [ + { + "projects": ["studio"], + "target": "build:web" + } + ], "options": { "outputPath": "dist/studio-web" } diff --git a/studio-web/vite.config.ts b/studio-web/vite.config.ts index c983e7b8..acfb880d 100644 --- a/studio-web/vite.config.ts +++ b/studio-web/vite.config.ts @@ -35,11 +35,15 @@ export default defineConfig({ fileName: "index", // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ["es"], + formats: ["es", "cjs"], }, rollupOptions: { // External packages that should not be bundled into your library. - external: [], + external: [ + "lit", + "@storyteller/framework", + "@storyteller/studio", + ], }, }, From 51eb15d05c951fd55ba70805889a67108ade65de Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 01:06:26 -0500 Subject: [PATCH 06/11] Fix indentation --- studio-web/vite.config.ts | 86 +++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/studio-web/vite.config.ts b/studio-web/vite.config.ts index acfb880d..40b53947 100644 --- a/studio-web/vite.config.ts +++ b/studio-web/vite.config.ts @@ -7,52 +7,52 @@ import topLevelAwait from "vite-plugin-top-level-await"; import * as path from "path"; export default defineConfig({ - cacheDir: "../node_modules/.vite/studio-web", + cacheDir: "../node_modules/.vite/studio-web", - plugins: [ - nxViteTsPaths(), - wasm(), + plugins: [ + nxViteTsPaths(), + wasm(), topLevelAwait(), - dts({ - entryRoot: "src", - tsConfigFilePath: path.join(__dirname, "tsconfig.lib.json"), - skipDiagnostics: true, - }), - ], + dts({ + entryRoot: "src", + tsConfigFilePath: path.join(__dirname, "tsconfig.lib.json"), + skipDiagnostics: true, + }), + ], - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: "src/index.ts", - name: "studio-web", - fileName: "index", - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ["es", "cjs"], - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: [ - "lit", - "@storyteller/framework", - "@storyteller/studio", - ], - }, - }, + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: "src/index.ts", + name: "studio-web", + fileName: "index", + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ["es", "cjs"], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [ + "lit", + "@storyteller/framework", + "@storyteller/studio", + ], + }, + }, - test: { - globals: true, - cache: { - dir: "../node_modules/.vitest", - }, - environment: "jsdom", - include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], - }, + test: { + globals: true, + cache: { + dir: "../node_modules/.vitest", + }, + environment: "jsdom", + include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + }, }); From d771dcd259091c8ef856e06b6adb01a7934c2688 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 01:25:25 -0500 Subject: [PATCH 07/11] Fix package-lock --- package-lock.json | 532 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 532 insertions(+) diff --git a/package-lock.json b/package-lock.json index b8d9010c..2fdbd5d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2684,6 +2684,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -4304,6 +4315,28 @@ "@types/chai": "*" } }, + "node_modules/@types/eslint": { + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", @@ -4526,6 +4559,181 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -4597,6 +4805,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -5276,6 +5494,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5378,6 +5606,13 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5905,6 +6140,20 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -5958,6 +6207,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true, + "peer": true + }, "node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", @@ -6013,6 +6269,30 @@ "node": ">=0.8.0" } }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -6038,6 +6318,19 @@ "node": ">=0.10" } }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -6068,6 +6361,16 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6424,6 +6727,13 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -7293,6 +7603,37 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", @@ -7455,6 +7796,16 @@ "@types/trusted-types": "^2.0.2" } }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, "node_modules/loader-utils": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", @@ -7637,6 +7988,13 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7788,6 +8146,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -8520,6 +8885,16 @@ } ] }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -8928,6 +9303,16 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -9291,6 +9676,16 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -9307,6 +9702,71 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -10039,6 +10499,20 @@ "node": ">=14" } }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -10057,6 +10531,64 @@ "node": ">=12" } }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", From 29b023c420a9e8abab795885929bbb1ce0fa8259 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 01:26:21 -0500 Subject: [PATCH 08/11] Hack Netlify command to build `react-frontend` to `studio-frontend` output path --- ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index c1f320f9..c4c4a924 100755 --- a/ci.sh +++ b/ci.sh @@ -4,4 +4,4 @@ set -euxo pipefail npm ci npx nx run studio:build:web -npx nx run studio-frontend:build:production +npx nx run react-frontend:build:production --output-path dist/studio-frontend From 871d365c387b6a294c89906b6fed4c52122b8921 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 01:28:59 -0500 Subject: [PATCH 09/11] Ignore a spurious type error --- react-frontend/src/app/studio-viewer.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/react-frontend/src/app/studio-viewer.tsx b/react-frontend/src/app/studio-viewer.tsx index a5a2ea50..0519f8eb 100644 --- a/react-frontend/src/app/studio-viewer.tsx +++ b/react-frontend/src/app/studio-viewer.tsx @@ -38,11 +38,7 @@ export default forwardRef(({ } return ( - + // @ts-ignore + ); }); From 8be6b0a523ea7a5365dc23655d691bf4a4fb3d1b Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 02:05:51 -0500 Subject: [PATCH 10/11] Fix a soft-lock trying to re-load the active scene --- studio/src/scene.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/studio/src/scene.rs b/studio/src/scene.rs index e9ef7399..b4cc5c09 100644 --- a/studio/src/scene.rs +++ b/studio/src/scene.rs @@ -1,4 +1,5 @@ use bevy::{ + asset::AssetPath, core_pipeline::{ bloom::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings}, fxaa::Fxaa, @@ -143,12 +144,24 @@ fn initialize(mut cmd: Commands) { fn init_scene_loading( mut cmd: Commands, assets: Res, + r_active_scene: Option>, mut er: EventReader, mut st_next: ResMut>, ) { if let Some(id) = er.read().last() { let name = &**id; - cmd.insert_resource(PendingScene(assets.load(format!("gltf/{name}.gltf")))); + let path = format!("gltf/{name}.gltf"); + + // Avoid trying to re-load this scene if it's already active + if let Some(active_scene_path) = + r_active_scene.and_then(|scene| scene.source.path().cloned()) + { + if active_scene_path == AssetPath::parse(&path) { + return; + } + } + + cmd.insert_resource(PendingScene(assets.load(path))); st_next.set(SceneState::Loading); } } From 70eddc4f89753ef7429d53a745ee03c54eac8be8 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Fri, 8 Dec 2023 02:06:49 -0500 Subject: [PATCH 11/11] Fix issue with `react-frontend` demo --- react-frontend/src/app/app.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/react-frontend/src/app/app.tsx b/react-frontend/src/app/app.tsx index 69408d92..5af94b39 100644 --- a/react-frontend/src/app/app.tsx +++ b/react-frontend/src/app/app.tsx @@ -82,6 +82,7 @@ const SKYBOXES = [ export default () => { const [objectId, setObjectId] = useState(null); const [skyboxId, setSkyboxId] = useState("meadow_4k"); + const [modalOpen, setModalOpen] = useState(false); const [loading, setLoading] = useState(true); const onSkyboxSelect = (event: FormEvent) => { @@ -100,8 +101,11 @@ export default () => { className={style.thumbnail} key={id} onClick={() => { - setLoading(true); - setObjectId(id); + if (id !== objectId) { + setLoading(true); + setObjectId(id); + } + setModalOpen(true); }} >

{label}

@@ -109,10 +113,10 @@ export default () => { ))} - {!!objectId && <> + {modalOpen && !!objectId && <>
setObjectId(null)} + onClick={() => setModalOpen(false)} />
{