274 lines
7.5 KiB
TypeScript
274 lines
7.5 KiB
TypeScript
import type { SceneGraph } from '@pascal-app/core/clone-scene-graph'
|
|
import type { AnyNode, AnyNodeId, AnyNodeType } from '@pascal-app/core/schema'
|
|
import type { ActiveSceneMeta, Patch, SceneBridge, ValidationResult } from '../bridge/scene-bridge'
|
|
import type {
|
|
SceneEvent,
|
|
SceneEventAppendOptions,
|
|
SceneEventListOptions,
|
|
SceneListOptions,
|
|
SceneMeta,
|
|
SceneMutateOptions,
|
|
SceneSaveOptions,
|
|
SceneStore,
|
|
SceneWithGraph,
|
|
} from '../storage/types'
|
|
|
|
export type CreateSceneOperationsOptions = {
|
|
bridge?: SceneBridge
|
|
store?: SceneStore
|
|
}
|
|
|
|
export interface SceneOperations {
|
|
readonly hasBridge: boolean
|
|
readonly hasStore: boolean
|
|
readonly hasSceneEvents: boolean
|
|
readonly canAppendSceneEvents: boolean
|
|
readonly canListSceneEvents: boolean
|
|
readonly storeBackend: SceneStore['backend'] | null
|
|
|
|
setActiveScene(meta: ActiveSceneMeta): void
|
|
getActiveScene(): ActiveSceneMeta | null
|
|
clearActiveScene(): void
|
|
loadDefault(): void
|
|
setScene(nodes: Record<AnyNodeId, AnyNode>, rootNodeIds: AnyNodeId[]): void
|
|
exportJSON(): SceneGraph & { collections: Record<string, unknown> }
|
|
exportSceneGraph(): SceneGraph
|
|
loadJSON(json: string | SceneGraph): void
|
|
getNode(id: AnyNodeId): AnyNode | null
|
|
getNodes(): Record<AnyNodeId, AnyNode>
|
|
getRootNodeIds(): AnyNodeId[]
|
|
getChildren(parentId: AnyNodeId): AnyNode[]
|
|
getAncestry(id: AnyNodeId): AnyNode[]
|
|
findNodes(filter: {
|
|
type?: AnyNodeType
|
|
parentId?: AnyNodeId | null
|
|
levelId?: AnyNodeId
|
|
}): AnyNode[]
|
|
resolveLevelId(id: AnyNodeId): AnyNodeId | null
|
|
createNode(node: AnyNode, parentId?: AnyNodeId): AnyNodeId
|
|
updateNode(id: AnyNodeId, data: Partial<AnyNode>): void
|
|
deleteNode(id: AnyNodeId, cascade?: boolean): string[]
|
|
applyPatch(patches: Patch[]): {
|
|
appliedOps: number
|
|
deletedIds: AnyNodeId[]
|
|
createdIds: AnyNodeId[]
|
|
}
|
|
undo(steps?: number): number
|
|
redo(steps?: number): number
|
|
validateScene(): ValidationResult
|
|
flushDirty(): string[]
|
|
getHistory(): { pastCount: number; futureCount: number }
|
|
clearHistory(): void
|
|
|
|
saveScene(options: SceneSaveOptions): Promise<SceneMeta>
|
|
loadStoredScene(id: string): Promise<SceneWithGraph | null>
|
|
listScenes(options?: SceneListOptions): Promise<SceneMeta[]>
|
|
deleteStoredScene(id: string, options?: SceneMutateOptions): Promise<boolean>
|
|
renameStoredScene(id: string, newName: string, options?: SceneMutateOptions): Promise<SceneMeta>
|
|
appendSceneEvent(options: SceneEventAppendOptions): Promise<SceneEvent | null>
|
|
listSceneEvents(id: string, options?: SceneEventListOptions): Promise<SceneEvent[]>
|
|
}
|
|
|
|
export function createSceneOperations(options: CreateSceneOperationsOptions): SceneOperations {
|
|
return new SceneOperationsFacade(options)
|
|
}
|
|
|
|
class SceneOperationsFacade implements SceneOperations {
|
|
readonly #bridge?: SceneBridge
|
|
readonly #store?: SceneStore
|
|
|
|
constructor(options: CreateSceneOperationsOptions) {
|
|
this.#bridge = options.bridge
|
|
this.#store = options.store
|
|
}
|
|
|
|
get hasBridge(): boolean {
|
|
return this.#bridge !== undefined
|
|
}
|
|
|
|
get hasStore(): boolean {
|
|
return this.#store !== undefined
|
|
}
|
|
|
|
get hasSceneEvents(): boolean {
|
|
return this.canAppendSceneEvents && this.canListSceneEvents
|
|
}
|
|
|
|
get canAppendSceneEvents(): boolean {
|
|
return typeof this.#store?.appendSceneEvent === 'function'
|
|
}
|
|
|
|
get canListSceneEvents(): boolean {
|
|
return typeof this.#store?.listSceneEvents === 'function'
|
|
}
|
|
|
|
get storeBackend(): SceneStore['backend'] | null {
|
|
return this.#store?.backend ?? null
|
|
}
|
|
|
|
setActiveScene(meta: ActiveSceneMeta): void {
|
|
this.requireBridge().setActiveScene(meta)
|
|
}
|
|
|
|
getActiveScene(): ActiveSceneMeta | null {
|
|
return this.requireBridge().getActiveScene()
|
|
}
|
|
|
|
clearActiveScene(): void {
|
|
this.requireBridge().clearActiveScene()
|
|
}
|
|
|
|
loadDefault(): void {
|
|
this.requireBridge().loadDefault()
|
|
}
|
|
|
|
setScene(nodes: Record<AnyNodeId, AnyNode>, rootNodeIds: AnyNodeId[]): void {
|
|
this.requireBridge().setScene(nodes, rootNodeIds)
|
|
}
|
|
|
|
exportJSON(): SceneGraph & { collections: Record<string, unknown> } {
|
|
return this.requireBridge().exportJSON()
|
|
}
|
|
|
|
exportSceneGraph(): SceneGraph {
|
|
const exported = this.exportJSON()
|
|
return {
|
|
nodes: exported.nodes,
|
|
rootNodeIds: exported.rootNodeIds,
|
|
collections: exported.collections as SceneGraph['collections'],
|
|
}
|
|
}
|
|
|
|
loadJSON(json: string | SceneGraph): void {
|
|
this.requireBridge().loadJSON(json)
|
|
}
|
|
|
|
getNode(id: AnyNodeId): AnyNode | null {
|
|
return this.requireBridge().getNode(id)
|
|
}
|
|
|
|
getNodes(): Record<AnyNodeId, AnyNode> {
|
|
return this.requireBridge().getNodes()
|
|
}
|
|
|
|
getRootNodeIds(): AnyNodeId[] {
|
|
return this.requireBridge().getRootNodeIds()
|
|
}
|
|
|
|
getChildren(parentId: AnyNodeId): AnyNode[] {
|
|
return this.requireBridge().getChildren(parentId)
|
|
}
|
|
|
|
getAncestry(id: AnyNodeId): AnyNode[] {
|
|
return this.requireBridge().getAncestry(id)
|
|
}
|
|
|
|
findNodes(filter: {
|
|
type?: AnyNodeType
|
|
parentId?: AnyNodeId | null
|
|
levelId?: AnyNodeId
|
|
}): AnyNode[] {
|
|
return this.requireBridge().findNodes(filter)
|
|
}
|
|
|
|
resolveLevelId(id: AnyNodeId): AnyNodeId | null {
|
|
return this.requireBridge().resolveLevelId(id)
|
|
}
|
|
|
|
createNode(node: AnyNode, parentId?: AnyNodeId): AnyNodeId {
|
|
return this.requireBridge().createNode(node, parentId)
|
|
}
|
|
|
|
updateNode(id: AnyNodeId, data: Partial<AnyNode>): void {
|
|
this.requireBridge().updateNode(id, data)
|
|
}
|
|
|
|
deleteNode(id: AnyNodeId, cascade?: boolean): string[] {
|
|
return this.requireBridge().deleteNode(id, cascade)
|
|
}
|
|
|
|
applyPatch(patches: Patch[]): {
|
|
appliedOps: number
|
|
deletedIds: AnyNodeId[]
|
|
createdIds: AnyNodeId[]
|
|
} {
|
|
return this.requireBridge().applyPatch(patches)
|
|
}
|
|
|
|
undo(steps?: number): number {
|
|
return this.requireBridge().undo(steps)
|
|
}
|
|
|
|
redo(steps?: number): number {
|
|
return this.requireBridge().redo(steps)
|
|
}
|
|
|
|
validateScene(): ValidationResult {
|
|
return this.requireBridge().validateScene()
|
|
}
|
|
|
|
flushDirty(): string[] {
|
|
return this.requireBridge().flushDirty()
|
|
}
|
|
|
|
getHistory(): { pastCount: number; futureCount: number } {
|
|
return this.requireBridge().getHistory()
|
|
}
|
|
|
|
clearHistory(): void {
|
|
this.requireBridge().clearHistory()
|
|
}
|
|
|
|
async saveScene(options: SceneSaveOptions): Promise<SceneMeta> {
|
|
return this.requireStore().save(options)
|
|
}
|
|
|
|
async loadStoredScene(id: string): Promise<SceneWithGraph | null> {
|
|
return this.requireStore().load(id)
|
|
}
|
|
|
|
async listScenes(options?: SceneListOptions): Promise<SceneMeta[]> {
|
|
return this.requireStore().list(options)
|
|
}
|
|
|
|
async deleteStoredScene(id: string, options?: SceneMutateOptions): Promise<boolean> {
|
|
return this.requireStore().delete(id, options)
|
|
}
|
|
|
|
async renameStoredScene(
|
|
id: string,
|
|
newName: string,
|
|
options?: SceneMutateOptions,
|
|
): Promise<SceneMeta> {
|
|
return this.requireStore().rename(id, newName, options)
|
|
}
|
|
|
|
async appendSceneEvent(options: SceneEventAppendOptions): Promise<SceneEvent | null> {
|
|
const append = this.requireStore().appendSceneEvent
|
|
if (!append) return null
|
|
return append(options)
|
|
}
|
|
|
|
async listSceneEvents(id: string, options?: SceneEventListOptions): Promise<SceneEvent[]> {
|
|
const list = this.requireStore().listSceneEvents
|
|
if (!list) {
|
|
throw new Error('scene_events_unavailable')
|
|
}
|
|
return list(id, options)
|
|
}
|
|
|
|
private requireBridge(): SceneBridge {
|
|
if (!this.#bridge) {
|
|
throw new Error('scene_bridge_unavailable')
|
|
}
|
|
return this.#bridge
|
|
}
|
|
|
|
private requireStore(): SceneStore {
|
|
if (!this.#store) {
|
|
throw new Error('scene_store_unavailable')
|
|
}
|
|
return this.#store
|
|
}
|
|
}
|