import * as studio from "@storyteller/studio"; import type { EntitySelectEvent, SeekTimeChanged, UpdateAnimationTargets } from "@storyteller/studio"; import { bind, drag, on } from "@storyteller/framework"; import { LitElement, type PropertyValues, html, unsafeCSS, TemplateResult, PropertyValueMap } from "lit"; import { customElement, state } from "lit/decorators.js"; import { styleMap } from "lit/directives/style-map.js"; import "@storyteller/studio-ui/button"; import "@storyteller/studio-ui/toolbar"; import "@storyteller/studio-ui/tree"; import "./anim-timeline.element"; import styles from "./anim-editor.element.scss?inline"; const TRACK_ICONS : Record = { camera: "video", transform: "up-down-left-right", light: "light" }; @customElement("sts-anim-editor") export class AnimEditorElement extends LitElement { static override styles = unsafeCSS(styles); @state() sidebarWidth = 256; @state() timelineStart = 0; @state() timelineEnd = 5; @state() clipStart = 0; @state() clipEnd = 15_000; @state() playHead = 0; @state() playing = false; @state() tracks : Record = {}; @state() track_ordered : string[] = []; @state() keyframes : Record = {}; @state() selected_track : string | false = false; trackCount(): number { return Object.keys(this.tracks).length; } protected override update(changes: PropertyValues): void { if (changes.has("playHead")) studio.seekTime(this.playHead); super.update(changes); } @on("window:seek-time-changed") onSeekTimeChanged({ detail: {time, playing} }: SeekTimeChanged ): void { console.log("Seek Time Changed", time, playing); this.playHead = time; this.playing = playing; } @on("window:update-animation-targets") onUpdateAnimationTargets({detail: { added, removed}}: UpdateAnimationTargets) : void { let addedKeys = Object.keys(added); if (addedKeys.length === 0 && removed.length === 0) { return; } console.log("Animation Targets Changed"); let tracks = {...this.tracks}; for (let track_entity of addedKeys) { let track = added[track_entity]; tracks[track_entity] = track; } for (let track_entity of removed) { delete tracks[track_entity]; } this.track_ordered = Object.keys(tracks); this.tracks = tracks; } @on("window:update-keyframes") onUpdateKeyframes({detail: {added, removed}}: studio.UpdateKeyframes): void { let addedKeyframes = Object.keys(added); if (addedKeyframes.length === 0 && removed.length === 0) { return; } console.log("Keyframes Changed"); let keyframes = {...this.keyframes}; for (let keyframe of addedKeyframes) { keyframes[keyframe] = added[keyframe]; } this.keyframes = keyframes; } @on("window:entity-select") onEntitySelected({ detail: id }: EntitySelectEvent): void { if (id && this.tracks[id]) { this.selected_track = id; } else { this.selected_track = false; } } onSidebarResize(event: PointerEvent): void { this.sidebarWidth = Math.max(Math.min(this.sidebarWidth + event.movementX, 512), 128); } addKeyframe(): void { if (this.selected_track) { studio.recordKeyframe([this.selected_track]); } } jumpToStart(): void { this.playHead = this.clipStart; } prevKeyframe(): void { let pos = this.playHead; this.playHead = pos; } nextKeyframe(): void { let pos = this.playHead; this.playHead = pos; } jumpToEnd(): void { this.playHead = this.clipEnd; } togglePlaying(): void { console.log("toggling play"); if (this.playing) { studio.pause(); } else { studio.play(); } } protected override render(): TemplateResult { let track_count = { '--track-count': `${this.trackCount()}`, '--row-count': `calc(var(--track-count) + 1)` }; const track_order_hashed : Record = {}; for (const [index, track] of this.track_ordered.entries()) { track_order_hashed[track] = index; } const tracklist = this.track_ordered.map((entity) => { let track = this.tracks[entity]; let name = track.name ?? track.target_type; let icon = TRACK_ICONS[track.target_type] ?? "minus"; return html` { studio.select(entity); }} > `; }); const none_selected = !this.selected_track; return html`
` }; };