awv3
Version:
⚡ AWV3 embedded CAD
260 lines (234 loc) • 9.11 kB
JavaScript
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()
}
}