UNPKG

awv3

Version:
260 lines (234 loc) 9.11 kB
import * as THREE from 'three' import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import Element from './element' import { reducer } from './store/index.js' import { actions as globalActions } from './store/globals' import { actions as elementActions } from './store/elements' import { actions as connectionActions, base as connectionBase } from './store/connections' import { base as pluginBase } from './store/plugins' import { createObserver, Pool, ObjectPrototype } from './helpers' import Selector from './selection/selector' import Connection from './connection' import Defaults from '../core/defaults' export default class Session { constructor(options = {}) { this.options = { throttle: 200, updateMaterials: false, centerGeometry: false, pool: Pool, objectPrototype: ObjectPrototype, ...Defaults.all, ...options, } const composeEnhancers = this.options.debug && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose this.store = options.store || createStore(reducer, composeEnhancers(applyMiddleware(thunk /*, logger*/))) // Support hot reloading reducers if (process.env.NODE_ENV !== 'production' && !options.store && module.hot) { module.hot.accept( ['./store/globals', './store/plugins', './store/elements', './store/connections', './store/index'], () => this.store.replaceReducer(require('./store/index.js').reducer), ) } this.pool = new Pool({ session: this, name: 'session.pool' }) this.defaultFeaturePlugin this.featurePlugins = {} this.materials = {} this.layerNames = [] this.observe = createObserver(this.store, this.getState.bind(this)) this.selector = new Selector(this, options) // Global observers this.observe(state => state.globals.activeConnection, observeActiveConnection.bind(this)) options.resources && this.addResources(options.resources) options.featurePlugins && this.registerFeaturePlugins(options.featurePlugins) options.defaultFeaturePlugin && this.registerDefaultFeaturePlugins(options.defaultFeaturePlugin) // Add a global keylistener that will forward events to a plugins console let keydownEventHandler = keydownEvent.bind(this) document.addEventListener('keydown', keydownEventHandler) // Link view if (this.options.view) { this.view = this.options.view this.view.scene.add(this.pool) } else { this.pool.viewFound().then(view => (this.view = view)) } // Add default connection for convenience if (this.options.defaultConnection) { let connection = this.addConnection('default') if (this.options.url && this.options.protocol) connection.on('connected', connection => this.options.defaultConnection(connection)) else this.options.defaultConnection(connection) } this.observe(state => state.globals.camera, state => (this.view.camera.orthographic = state === 'orthographic')) this.observe( state => state.globals.day, day => (day ? document.body.classList.remove('night') : document.body.classList.add('night')), { fireOnStart: true } ) } showLayer(args = []) { this.layerNames = Array.isArray(args) ? args : [args] this.pool.traverse(obj => { if (obj.material) { let isMultiMaterial = Array.isArray(obj.material) if (isMultiMaterial) { obj.material.forEach( mat => (mat.visible = mat.meta ? this.layerNames.length == 0 || this.layerNames.indexOf(mat.meta.layer) > -1 : mat.visible), ) } else if (obj.material.meta) { obj.material.visible = this.layerNames.length == 0 || this.layerNames.indexOf(obj.material.meta.layer) > -1 } } }) this.pool.view.invalidate() } isVisibleLayer(layerName) { if (this.layerNames.length === 0 || this.layerNames.indexOf(layerName) > -1) return true return false } getState() { return this.store.getState() || {} } dispatch(action) { return this.store.dispatch(action) } registerDefaultFeaturePlugin(prototype) { this.defaultFeaturePlugin = prototype } registerFeaturePlugins(map) { this.featurePlugins = { ...this.featurePlugins, ...map } } async resolveFeaturePlugin(promise, ...args) { let { default: plugin } = await promise this.registerFeaturePlugins(args.reduce((prev, key) => ({ ...prev, [key]: plugin }), {})) } async resolveDefaultFeaturePlugin(promise) { let { default: Plugin } = await promise this.registerDefaultFeaturePlugin(Plugin) } async resolveGlobalPlugins(...promises) { let plugins = (await Promise.all(promises)).map(({ default: Plugin }) => new Plugin(session).id) session.dispatch(globalActions.linkPlugins(plugins)) } findFeaturePlugin(id) { return this.featurePlugins.find(item => item.id === id) } addResources(map) { this.dispatch(globalActions.addResources(map)) } addConnection(name) { let connection = new Connection(this, { name }) this.store.dispatch(globalActions.setActiveConnection(connection.id)) return connection } resolveResource(name, plugin) { let resource let scope = this.plugins[plugin] if (scope) resource = scope.resources[name] if (!resource) resource = this.globals.resources[name] return resource } resolveTree(args) { let id = this.globals.activeConnection let connection = connectionBase.references[id] return connection.resolveTree(args) } printSceneGraph() { let count = 1 const traverse = (obj, level = 0) => { if (obj) { obj.children.forEach(item => { console.log( `${count++}:\t${Array.from(new Array(level)).fill(' ').join('')}${item.type}${item.name ? '_' + item.name : ''}`, ) traverse(item, level + 2) }) } } traverse(this.scene) } get scene() { return this.pool.scene } get state() { return this.getState() } get globals() { return this.state.globals } get plugins() { return this.state.plugins } get elements() { return this.state.elements } get connections() { return this.state.connections } get collections() { return this.state.collections } // Computed props get activeConnection() { return this.state.connections[this.state.globals.activeConnection] } get activePlugin() { return this.plugins[ this.activeConnection.plugins.find( item => this.plugins[item].feature === this.activeConnection.activeFeature, ) ] } get tree() { return this.activeConnection.tree } get activeConnectionClass() { return connectionBase.references[this.globals.activeConnection] } get activePluginClass() { return pluginBase.references[this.activePlugin.id] } } function checkElements(session, elements) { for (let key of elements) { let element = session.elements[key] if (element.type === Element.Type.Console) { session.store.dispatch(elementActions.update(element.id, { focus: true })) session.store.dispatch(elementActions.event(element.id, event)) return true } else if (element.type === Element.Type.Group && checkElements(session, element.children)) { return true } } return false } function keydownEvent(event) { let target = event.target if (!(target instanceof HTMLInputElement)) { for (let key in this.plugins) { let plugin = this.plugins[key] if (plugin.enabled && !plugin.collapsed && checkElements(this, plugin.elements)) { return } } } } async function observeActiveConnection(id, previous) { if (previous) this.pool.removeAsync(connectionBase.references[previous].pool) if (id) { let activeConnection = connectionBase.references[id] await this.pool.addAsync(activeConnection.pool) await this.pool.viewFound() this.pool.view.updateBounds() this.pool.view.controls.focus().zoom() } }