mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-14 23:53:14 +00:00
c9904a5293
This PR begins fleshing out the front-end UI for inspecting the selected entity's components. ### Implemented * Support for the `Name` component * Placeholder/WIP support for the `Transform` component ### To-do * Support for sending changes made in the inspector back to Bevy * A slider/field combo widget for numeric values (can be adapted from [this StackBlitz](https://stackblitz.com/edit/angular-wfikuy?file=src%2Ffloat-input%2Ffloat-input.component.ts)) * Quat -> Euler angles conversion ([example math here](https://github.com/dannymcgee/3DGameMath/blob/5cb7a8ff9310565329e57751cd8b9c33e9bd4cb7/libs/Math/include/math/quat.inl.hpp#L56-L78), or use a 3rd-party math library) ### Known issues * Selecting entities from the hierarchy doesn't always update the components list. I suspect this is the same issue as #39
115 lines
2.8 KiB
TypeScript
115 lines
2.8 KiB
TypeScript
import { type DynamicProvider, inject, UniqueToken } from "@storyteller/framework";
|
|
import { LitElement, type PropertyValues, html, nothing, unsafeCSS } from "lit";
|
|
import { customElement, property } from "lit/decorators.js";
|
|
import { classMap } from "lit/directives/class-map.js";
|
|
import { ifDefined } from "lit/directives/if-defined.js";
|
|
|
|
import "./icon.element";
|
|
|
|
import styles from "./tree.element.scss?inline";
|
|
|
|
export const TREE_SELECTION_PROVIDER = UniqueToken.create<TreeSelectionProvider>();
|
|
|
|
export interface TreeSelectionProvider extends DynamicProvider<TreeSelectionProvider> {
|
|
readonly selectedId: string | null;
|
|
}
|
|
|
|
export interface Tree {
|
|
id?: string;
|
|
name: string;
|
|
icon?: string;
|
|
children?: Tree[];
|
|
}
|
|
|
|
@customElement("sts-tree")
|
|
export class TreeElement extends LitElement {
|
|
static override styles = unsafeCSS(styles);
|
|
|
|
@property() name = "";
|
|
@property() icon?: string;
|
|
@property({ attribute: false }) treeChildren?: Tree[];
|
|
@property({ type: Boolean }) expanded = true;
|
|
|
|
get selected() {
|
|
return !!this.id && this._selectionProvider?.selectedId === this.id
|
|
}
|
|
|
|
@inject(TREE_SELECTION_PROVIDER)
|
|
_selectionProvider?: TreeSelectionProvider;
|
|
|
|
override connectedCallback(): void {
|
|
this._selectionProvider?.addEventListener("property-changes", this.onProviderChanges);
|
|
super.connectedCallback();
|
|
}
|
|
|
|
override disconnectedCallback(): void {
|
|
this._selectionProvider?.removeEventListener("property-changes", this.onProviderChanges);
|
|
super.disconnectedCallback();
|
|
}
|
|
|
|
onProviderChanges = ({ detail: changes }: CustomEvent<PropertyValues<TreeSelectionProvider>>) => {
|
|
if (changes.has("selectedId"))
|
|
this.requestUpdate();
|
|
}
|
|
|
|
toggleExpanded(): void {
|
|
this.expanded = !this.expanded;
|
|
}
|
|
|
|
selectEntity(): void {
|
|
if (this.id) {
|
|
this.dispatchEvent(new CustomEvent("tree-select", {
|
|
detail: this.id,
|
|
bubbles: true,
|
|
composed: true,
|
|
}));
|
|
}
|
|
}
|
|
|
|
// TODO: A11y
|
|
protected override render = () => html`
|
|
<header
|
|
class=${classMap({
|
|
selected: this.selected,
|
|
selectable: !!this.id,
|
|
})}
|
|
>
|
|
${this.treeChildren?.length ? html`
|
|
<button class="toggle-expanded"
|
|
@click=${this.toggleExpanded}
|
|
>
|
|
<sts-icon
|
|
class=${classMap({
|
|
caret: true,
|
|
expanded: this.expanded,
|
|
})}
|
|
icon="caret-right"
|
|
aria-label=${this.expanded ? "Expand" : "Collapse"}
|
|
></sts-icon>
|
|
</button>
|
|
` : nothing}
|
|
<span class="label"
|
|
@click=${this.selectEntity}
|
|
>
|
|
${this.icon ? html`
|
|
<sts-icon class="icon" icon=${this.icon}></sts-icon>
|
|
` : nothing}
|
|
${this.name}
|
|
</span>
|
|
</header>
|
|
|
|
${this.expanded && this.treeChildren?.length ? html`
|
|
<div class="children">
|
|
${this.treeChildren?.map(tree => html`
|
|
<sts-tree
|
|
id=${ifDefined(tree.id)}
|
|
.name=${tree.name}
|
|
.icon=${tree.icon}
|
|
.treeChildren=${tree.children}
|
|
></sts-tree>
|
|
`)}
|
|
</div>
|
|
` : nothing}
|
|
`;
|
|
}
|