awv3
Version:
⚡ AWV3 embedded CAD
257 lines (229 loc) • 8.97 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');
}
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();
}
}