/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * LocalBackend — implements BimBackend via per-namespace adapters. * * This is the viewer's internal backend: zero serialization overhead. * Each namespace is a typed property with named methods. */ import type { BimBackend, BimEventType, ModelBackendMethods, QueryBackendMethods, SelectionBackendMethods, VisibilityBackendMethods, ViewerBackendMethods, MutateBackendMethods, StoreBackendMethods, SpatialBackendMethods, ExportBackendMethods, LensBackendMethods, FilesBackendMethods, ScheduleBackendMethods, } from '@ifc-lite/sdk'; import type { StoreApi } from './adapters/types.js'; import { LEGACY_MODEL_ID } from './adapters/model-compat.js'; import { createModelAdapter } from './adapters/model-adapter.js'; import { createQueryAdapter } from './adapters/query-adapter.js'; import { createSelectionAdapter } from './adapters/selection-adapter.js'; import { createVisibilityAdapter } from './adapters/visibility-adapter.js'; import { createViewerAdapter } from './adapters/viewer-adapter.js'; import { createMutateAdapter } from './adapters/mutate-adapter.js'; import { createStoreAdapter } from './adapters/store-adapter.js'; import { createSpatialAdapter } from './adapters/spatial-adapter.js'; import { createLensAdapter } from './adapters/lens-adapter.js'; import { createExportAdapter } from './adapters/export-adapter.js'; import { createFilesAdapter } from './adapters/files-adapter.js'; import { createScheduleAdapter } from './adapters/schedule-adapter.js'; export class LocalBackend implements BimBackend { readonly model: ModelBackendMethods; readonly query: QueryBackendMethods; readonly selection: SelectionBackendMethods; readonly visibility: VisibilityBackendMethods; readonly viewer: ViewerBackendMethods; readonly mutate: MutateBackendMethods; readonly store: StoreBackendMethods; readonly spatial: SpatialBackendMethods; readonly export: ExportBackendMethods; readonly lens: LensBackendMethods; readonly files: FilesBackendMethods; readonly schedule: ScheduleBackendMethods; private storeApi: StoreApi; constructor(store: StoreApi) { this.storeApi = store; this.model = createModelAdapter(store); this.query = createQueryAdapter(store); this.selection = createSelectionAdapter(store); this.visibility = createVisibilityAdapter(store); this.viewer = createViewerAdapter(store); this.mutate = createMutateAdapter(store); this.store = createStoreAdapter(store); this.spatial = createSpatialAdapter(store); this.lens = createLensAdapter(store); this.export = createExportAdapter(store); this.files = createFilesAdapter(store); this.schedule = createScheduleAdapter(store); } subscribe(event: BimEventType, handler: (data: unknown) => void): () => void { switch (event) { case 'selection:changed': return this.storeApi.subscribe((state, prev) => { if (state.selectedEntities !== prev.selectedEntities) { handler({ refs: state.selectedEntities ?? [] }); } }); case 'model:loaded': return this.storeApi.subscribe((state, prev) => { if (state.models.size > prev.models.size) { for (const [id, model] of state.models) { if (!prev.models.has(id)) { handler({ model: { id: model.id, name: model.name, schema: model.schemaVersion, schemaVersion: model.schemaVersion, entityCount: model.ifcDataStore?.entities?.count ?? 0, fileSize: model.fileSize, loadedAt: model.loadedAt, }, }); } } } if (state.ifcDataStore && !prev.ifcDataStore && state.models.size === 0) { handler({ model: { id: LEGACY_MODEL_ID, name: 'Model', schema: state.ifcDataStore.schemaVersion ?? 'IFC4', schemaVersion: state.ifcDataStore.schemaVersion ?? 'IFC4', entityCount: state.ifcDataStore.entities?.count ?? 0, fileSize: state.ifcDataStore.source?.byteLength ?? 0, loadedAt: 0, }, }); } }); case 'model:removed': return this.storeApi.subscribe((state, prev) => { if (state.models.size < prev.models.size) { for (const id of prev.models.keys()) { if (!state.models.has(id)) { handler({ modelId: id }); } } } }); case 'visibility:changed': return this.storeApi.subscribe((state, prev) => { if ( state.hiddenEntities !== prev.hiddenEntities || state.isolatedEntities !== prev.isolatedEntities || state.hiddenEntitiesByModel !== prev.hiddenEntitiesByModel ) { handler({}); } }); case 'mutation:changed': return this.storeApi.subscribe((state, prev) => { if (state.mutationVersion !== prev.mutationVersion) { handler({}); } }); case 'lens:changed': return this.storeApi.subscribe((state, prev) => { if (state.activeLensId !== prev.activeLensId) { handler({ lensId: state.activeLensId }); } }); default: return () => {}; } } }