├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build ├── karma.config.js ├── lib │ └── logger.js ├── scripts │ ├── compile.js │ └── start.js └── webpack.config.js ├── docs └── assets │ └── images │ ├── inviewer-preview-new.PNG │ └── inviewer-preview.JPG ├── package-lock.json ├── package.json ├── project.config.js ├── public ├── favicon.ico ├── humans.txt └── robots.txt ├── resources └── img │ └── logos │ └── indoorgml.png ├── server ├── api │ ├── iGml.js │ └── iJson.js ├── main.js ├── services │ ├── BaseSvc.js │ ├── SocketSvc.js │ ├── SvcManager.js │ └── UploadSvc.js └── utils │ └── IndoorGML_Core_1_0_3.js ├── src ├── components │ ├── BaseComponent │ │ ├── BaseComponent.js │ │ └── index.js │ ├── ClientAPI │ │ ├── ClientAPI.js │ │ └── index.js │ ├── Dialogs │ │ ├── AboutDlg │ │ │ ├── AboutDlg.js │ │ │ ├── AboutDlg.scss │ │ │ └── index.js │ │ ├── DatabaseDlg │ │ │ ├── DatabaseDlg.js │ │ │ ├── DatabaseDlg.scss │ │ │ └── index.js │ │ ├── LayoutDlg │ │ │ ├── LayoutDlg.js │ │ │ ├── LayoutDlg.scss │ │ │ └── index.js │ │ ├── ServiceDlg │ │ │ ├── ServiceDlg.js │ │ │ ├── ServiceDlg.scss │ │ │ └── index.js │ │ ├── ThemeDlg │ │ │ ├── ThemeDlg.js │ │ │ ├── ThemeDlg.scss │ │ │ └── index.js │ │ └── dialogs.scss │ ├── EventsEmitter │ │ ├── EventsEmitter.js │ │ └── index.js │ ├── ExtensionPane │ │ ├── ExtensionPane.js │ │ └── index.js │ ├── FileUpload │ │ ├── FileUpload.js │ │ └── index.js │ ├── Header │ │ ├── Header.js │ │ ├── Header.scss │ │ └── index.js │ ├── IGMLHelper │ │ ├── IGMLHelper.js │ │ ├── index.js │ │ └── node.js │ ├── IJSONHelper │ │ ├── IJSONHelper.js │ │ └── index.js │ ├── Indoor │ │ ├── CellSpaceBoundary.js │ │ ├── Cellspace.js │ │ ├── IGMLParser.js │ │ ├── Indoor.js │ │ ├── LineString.js │ │ ├── Point.js │ │ ├── Polygon.js │ │ ├── SpaceLayer.js │ │ ├── State.js │ │ ├── Transition.js │ │ └── index.js │ ├── Menubar │ │ ├── Menubar.js │ │ ├── Menubar.scss │ │ └── index.js │ ├── ModelUploader │ │ ├── ModelUploader.js │ │ ├── ModelUploader.scss │ │ └── index.js │ ├── PaneManager │ │ ├── PaneElement.js │ │ ├── PaneManager.js │ │ ├── PaneManager.scss │ │ └── index.js │ ├── Panel │ │ ├── Panel.js │ │ ├── Panel.scss │ │ └── index.js │ ├── Settings │ │ └── index.js │ ├── Stopwatch │ │ ├── Stopwatch.js │ │ └── index.js │ ├── ThreeViewer │ │ ├── Config.js │ │ ├── Light.js │ │ ├── Renderer.js │ │ ├── ThreeViewer.js │ │ ├── Viewport.js │ │ └── index.js │ ├── ThreeViewerMock │ │ ├── ThreeViewerMock.js │ │ └── index.js │ ├── Uploader │ │ ├── Uploader.js │ │ ├── Uploader.scss │ │ └── index.js │ └── Viewer │ │ └── components │ │ ├── Viewer.App │ │ ├── ViewerApp.js │ │ ├── ViewerApp.scss │ │ └── index.js │ │ ├── Viewer.Container │ │ ├── Viewer.Container.js │ │ ├── Viewer.Container.scss │ │ └── index.js │ │ ├── Viewer.ExtensionBase │ │ ├── Viewer.ExtensionBase.js │ │ └── index.js │ │ ├── Viewer.ExtensionManager │ │ ├── Viewer.ExtensionManager.js │ │ ├── Viewer.ExtensionManager.scss │ │ └── index.js │ │ └── Viewer.Extensions │ │ └── README.md ├── containers │ └── AppContainer.js ├── electron.js ├── index.html ├── index.js ├── layouts │ ├── CoreLayout │ │ ├── CoreLayout.js │ │ ├── CoreLayoutContainer.js │ │ └── index.js │ └── PageLayout │ │ ├── PageLayout.js │ │ └── PageLayout.scss ├── normalize.js ├── routes │ ├── Configurator │ │ ├── ConfiguratorView │ │ │ ├── ConfiguratorHomeView │ │ │ │ ├── ConfiguratorHomeView.js │ │ │ │ ├── ConfiguratorHomeView.scss │ │ │ │ ├── ConfiguratorItem.js │ │ │ │ └── index.js │ │ │ ├── ConfiguratorView.js │ │ │ ├── ConfiguratorView.scss │ │ │ └── index.js │ │ ├── containers │ │ │ └── ConfiguratorContainer.js │ │ ├── index.js │ │ └── modules │ │ │ └── configurator.js │ ├── Counter │ │ ├── components │ │ │ └── Counter.js │ │ ├── containers │ │ │ └── CounterContainer.js │ │ ├── index.js │ │ └── modules │ │ │ └── counter.js │ ├── Home │ │ ├── assets │ │ │ └── Duck.jpg │ │ ├── components │ │ │ ├── HomeView.js │ │ │ └── HomeView.scss │ │ └── index.js │ ├── Main │ │ ├── components │ │ │ ├── Main.js │ │ │ └── Main.scss │ │ ├── containers │ │ │ └── MainContainer.js │ │ ├── index.js │ │ └── modules │ │ │ └── main.js │ └── index.js ├── services │ ├── BaseSvc.js │ ├── DialogSvc.js │ ├── EventSvc.js │ ├── NotifySvc.js │ ├── SocketSvc.js │ ├── StorageSvc.js │ └── SvcManager.js ├── store │ ├── app.js │ ├── createStore.js │ ├── index.js │ ├── location.js │ └── reducers.js └── styles │ ├── _base.scss │ └── core.scss └── tests ├── .eslintrc ├── layouts └── PageLayout.spec.js ├── routes ├── Counter │ ├── components │ │ └── Counter.spec.js │ ├── index.spec.js │ └── modules │ │ └── counter.spec.js └── Home │ ├── components │ └── HomeView.spec.js │ └── index.spec.js ├── store ├── createStore.spec.js └── location.spec.js └── test-bundler.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # A special property that should be specified at the top of the file outside of 4 | # any sections. Set to true to stop .editor config file search on current file 5 | root = true 6 | 7 | [*] 8 | # Indentation style 9 | # Possible values - tab, space 10 | indent_style = space 11 | 12 | # Indentation size in single-spaced characters 13 | # Possible values - an integer, tab 14 | indent_size = 2 15 | 16 | # Line ending file format 17 | # Possible values - lf, crlf, cr 18 | end_of_line = lf 19 | 20 | # File character encoding 21 | # Possible values - latin1, utf-8, utf-16be, utf-16le 22 | charset = utf-8 23 | 24 | # Denotes whether to trim whitespace at the end of lines 25 | # Possible values - true, false 26 | trim_trailing_whitespace = true 27 | 28 | # Denotes whether file should end with a newline 29 | # Possible values - true, false 30 | insert_final_newline = true 31 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | node_modules/** 3 | dist/** 4 | src/index.html 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react" 6 | ], 7 | "plugins": [ 8 | "babel", 9 | "react", 10 | "promise" 11 | ], 12 | "env": { 13 | "browser" : true 14 | }, 15 | "globals": { 16 | "__DEV__" : false, 17 | "__TEST__" : false, 18 | "__PROD__" : false, 19 | "__COVERAGE__" : false 20 | }, 21 | "rules": { 22 | "key-spacing" : "off", 23 | "jsx-quotes" : [2, "prefer-single"], 24 | "max-len" : [2, 120, 2], 25 | "object-curly-spacing" : [2, "always"], 26 | "comma-dangle" : "off" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *.log 3 | node_modules 4 | dist 5 | coverage 6 | .idea/ 7 | .yarn-cache 8 | /TMP 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "5" 5 | - "6" 6 | 7 | cache: 8 | yarn: true 9 | directories: 10 | - node_modules 11 | 12 | script: 13 | - yarn lint 14 | - yarn test 15 | - yarn build 16 | 17 | after_success: 18 | - yarn codecov 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 STEMLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InViewer 2 | 3 | A Three.js-based viewer for visualizing OGC IndoorGML data 4 | 5 | ![Image of preview](docs/assets/images/inviewer-preview-new.PNG) 6 | 7 | ## Main Features 8 | - Visualizing OGC IndoorGML data 9 | - Interactive Visualizing IndoorGML with [InEditor] 10 | 11 | ## Prerequisites 12 | 13 | - Node.js >= ^5.0.0 14 | - npm >= ^3.0.0 15 | - Python 2.7 16 | 17 | ## Quick Start 18 | 19 | 1. You need to install modules. Please follow step. 20 | ``` 21 | npm install 22 | ``` 23 | 24 | 2. Start server by executing `npm start` and launch a web browser and open the viewer (http://127.0.0.1:3000). 25 | 26 | ## Building for Production 27 | 28 | TBD 29 | 30 | ## Project Structure 31 | 32 | ``` 33 | . 34 | ├── build # All build-related code 35 | ├── docs # Assets for documentation 36 | ├── public # Static public assets 37 | ├── server # Express application that provides webpack middleware 38 | │ └── main.js # Server application entry point 39 | ├── src # Application source code 40 | │ ├── index.html # Main HTML page container for app 41 | │ ├── index.js # Application bootstrap and rendering 42 | │ ├── normalize.js # Browser normalization and polyfills 43 | │ ├── components # Global Reusable Components 44 | │ ├── containers # Global Reusable Container Components 45 | │ ├── layouts # Components that dictate major page structure 46 | │ │ └── CoreLayout # Global application layout in which to render routes 47 | │ ├── routes # Main route definitions and async split points 48 | │ │ ├── index.js # Bootstrap main application routes with store 49 | │ │ ├── Main # The main route 50 | │ │ │ ├── index.js # Route definitions and async split points 51 | │ │ │ ├── assets # Assets required to render components 52 | │ │ │ ├── components # Presentational React Components 53 | │ │ │ ├── container # Connect components to actions and store 54 | │ │ └── └── modules # Collections of reducers/constants/actions 55 | │ ├── services # Global service manager based on EventsEmitter 56 | │ ├── store # Redux-specific pieces 57 | │ │ ├── createStore.js # Create and instrument redux store 58 | │ │ └── reducers.js # Reducer registry and injection 59 | │ └── styles # Application-wide styles (generally settings) 60 | └── tests # Unit tests 61 | ``` 62 | 63 | ## Authors 64 | 65 | Name | E-mail | Affiliation 66 | --- | --- | --- 67 | **Hyung-Gyu Ryoo** | hgryoo@pnu.edu | Pusan National University 68 | **Suhee Jung** | lalune1120@hotmail.com | Pusan National University 69 | **Soojin Kim** | gini.pig@kakaocorp.com | Kakao Mobility 70 | 71 | ## Note 72 | 73 | This project is based on the cool [React Redux Starter Kit](https://github.com/davezuko/react-redux-starter-kit) and [forge-rcdb.node.js](https://github.com/Autodesk-Forge/forge-rcdb.nodejs) 74 | -------------------------------------------------------------------------------- /build/karma.config.js: -------------------------------------------------------------------------------- 1 | const argv = require('yargs').argv 2 | const webpackConfig = require('./webpack.config') 3 | 4 | const TEST_BUNDLER = './tests/test-bundler.js' 5 | 6 | const karmaConfig = { 7 | basePath: '../', 8 | browsers: ['PhantomJS'], 9 | singleRun: !argv.watch, 10 | coverageReporter: { 11 | reporters: [ 12 | { type: 'text-summary' }, 13 | ], 14 | }, 15 | files: [{ 16 | pattern : TEST_BUNDLER, 17 | watched : false, 18 | served : true, 19 | included : true 20 | }], 21 | frameworks: ['mocha'], 22 | reporters: ['mocha'], 23 | preprocessors: { 24 | [TEST_BUNDLER]: ['webpack'], 25 | }, 26 | logLevel: 'WARN', 27 | browserConsoleLogOptions: { 28 | terminal: true, 29 | format: '%b %T: %m', 30 | level: '', 31 | }, 32 | webpack: { 33 | entry: TEST_BUNDLER, 34 | devtool: 'cheap-module-source-map', 35 | module: webpackConfig.module, 36 | plugins: webpackConfig.plugins, 37 | resolve: webpackConfig.resolve, 38 | externals: { 39 | 'react/addons': 'react', 40 | 'react/lib/ExecutionEnvironment': 'react', 41 | 'react/lib/ReactContext': 'react', 42 | }, 43 | }, 44 | webpackMiddleware: { 45 | stats: 'errors-only', 46 | noInfo: true, 47 | }, 48 | } 49 | 50 | module.exports = (cfg) => cfg.set(karmaConfig) 51 | -------------------------------------------------------------------------------- /build/lib/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const figures = require('figures') 3 | 4 | // Need to support Node versions that don't support spreading function arguments 5 | const spread = (fn) => function () { 6 | return fn([].slice.call(arguments)) 7 | } 8 | 9 | exports.log = console.log.bind(console) 10 | 11 | exports.error = spread((messages) => { 12 | console.error(chalk.red.apply(chalk, [figures.cross].concat(messages))) 13 | }) 14 | 15 | exports.info = spread((messages) => { 16 | console.info(chalk.cyan.apply(chalk, [figures.info].concat(messages))) 17 | }) 18 | 19 | exports.success = spread((messages) => { 20 | console.log(chalk.green.apply(chalk, [figures.tick].concat(messages))) 21 | }) 22 | 23 | exports.warn = spread((messages) => { 24 | console.warn(chalk.yellow.apply(chalk, [figures.warning].concat(messages))) 25 | }) 26 | -------------------------------------------------------------------------------- /build/scripts/compile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const chalk = require('chalk') 4 | const webpack = require('webpack') 5 | const logger = require('../lib/logger') 6 | const webpackConfig = require('../webpack.config') 7 | const project = require('../../project.config') 8 | 9 | const runWebpackCompiler = (webpackConfig) => 10 | new Promise((resolve, reject) => { 11 | webpack(webpackConfig).run((err, stats) => { 12 | if (err) { 13 | logger.error('Webpack compiler encountered a fatal error.', err) 14 | return reject(err) 15 | } 16 | 17 | const jsonStats = stats.toJson() 18 | if (jsonStats.errors.length > 0) { 19 | logger.error('Webpack compiler encountered errors.') 20 | logger.log(jsonStats.errors.join('\n')) 21 | return reject(new Error('Webpack compiler encountered errors')) 22 | } else if (jsonStats.warnings.length > 0) { 23 | logger.warn('Webpack compiler encountered warnings.') 24 | logger.log(jsonStats.warnings.join('\n')) 25 | } 26 | resolve(stats) 27 | }) 28 | }) 29 | 30 | const compile = () => Promise.resolve() 31 | .then(() => logger.info('Starting compiler...')) 32 | .then(() => logger.info('Target application environment: ' + chalk.bold(project.env))) 33 | .then(() => runWebpackCompiler(webpackConfig)) 34 | .then((stats) => { 35 | logger.info(`Copying static assets from ./public to ./${project.outDir}.`) 36 | fs.copySync( 37 | path.resolve(project.basePath, 'public'), 38 | path.resolve(project.basePath, project.outDir) 39 | ) 40 | return stats 41 | }) 42 | .then((stats) => { 43 | if (project.verbose) { 44 | logger.log(stats.toString({ 45 | colors: true, 46 | chunks: false, 47 | })) 48 | } 49 | logger.success(`Compiler finished successfully! See ./${project.outDir}.`) 50 | }) 51 | .catch((err) => logger.error('Compiler encountered errors.', err)) 52 | 53 | compile() 54 | -------------------------------------------------------------------------------- /build/scripts/start.js: -------------------------------------------------------------------------------- 1 | const logger = require('../lib/logger') 2 | 3 | logger.info('Starting server...') 4 | require('../../server/main')(); 5 | -------------------------------------------------------------------------------- /docs/assets/images/inviewer-preview-new.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEMLab/InViewer/f03df4cbdea47317ee2dc333b9869a05cfe6b6e1/docs/assets/images/inviewer-preview-new.PNG -------------------------------------------------------------------------------- /docs/assets/images/inviewer-preview.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEMLab/InViewer/f03df4cbdea47317ee2dc333b9869a05cfe6b6e1/docs/assets/images/inviewer-preview.JPG -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "InViewer", 3 | "version": "0.9.0", 4 | "description": "InViewer - IndoorGML Viewer", 5 | "main": "src/electron.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "compile": "node build/scripts/compile", 9 | "build": "npm run clean && cross-env NODE_ENV=production npm run compile", 10 | "start": "cross-env NODE_ENV=development nodemon build/scripts/start --exec babel-node", 11 | "test": "cross-env NODE_ENV=test karma start build/karma.config", 12 | "test:watch": "npm test -- --watch", 13 | "lint": "eslint .", 14 | "lint:fix": "npm run lint -- --fix" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/STEMLab/react-redux-starter-kit.git" 19 | }, 20 | "author": [ 21 | "Hyung-Gyu Ryoo (@hg_ryoo)", 22 | "Suhee Jung 12 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /resources/img/logos/indoorgml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEMLab/InViewer/f03df4cbdea47317ee2dc333b9869a05cfe6b6e1/resources/img/logos/indoorgml.png -------------------------------------------------------------------------------- /server/api/iGml.js: -------------------------------------------------------------------------------- 1 | 2 | import ServiceManager from '../services/SvcManager' 3 | import express from 'express' 4 | import path from 'path' 5 | 6 | import IndoorGML_Core_1_0_3_Module_Factory from '../utils/IndoorGML_Core_1_0_3' 7 | import BGJsonix from 'jsonix' 8 | 9 | module.exports = function() { 10 | 11 | ///////////////////////////////////////////////////////// 12 | // 13 | // 14 | ///////////////////////////////////////////////////////// 15 | const router = express.Router() 16 | 17 | const uploadSvc = ServiceManager.getService( 18 | 'UploadSvc') 19 | ///////////////////////////////////////////////////////////////////////////// 20 | // POST /igml/:filename 21 | // Post IndoorGML file 22 | // 23 | ///////////////////////////////////////////////////////////////////////////// 24 | router.post('/:filename', 25 | uploadSvc.uploader.single('file'), 26 | async(req, res) => { 27 | 28 | try { 29 | console.log("got it!"); 30 | if (!req.file) 31 | return res.status(400).json('No files were uploaded.'); 32 | 33 | const file = req.file 34 | 35 | var IndoorGML_Core_1_0_3 = IndoorGML_Core_1_0_3_Module_Factory().IndoorGML_Core_1_0_3; 36 | var mappings = [IndoorGML_Core_1_0_3]; 37 | var context = new BGJsonix.Jsonix.Context(mappings); 38 | var unmarshaller = context.createUnmarshaller(); 39 | 40 | console.log(file.path); 41 | 42 | var resume = unmarshaller.unmarshalFile(file.path, function(result) { 43 | console.log("1.0.3 >> converting complete"); 44 | 45 | var responseData = JSON.stringify(result, null, null); 46 | res.json(responseData); 47 | 48 | console.log("1.0.3 >> send json"); 49 | }); 50 | 51 | } catch (error) { 52 | res.status(error.statusCode || 500) 53 | res.json(error) 54 | } 55 | }) 56 | 57 | return router 58 | } 59 | -------------------------------------------------------------------------------- /server/api/iJson.js: -------------------------------------------------------------------------------- 1 | import ServiceManager from '../services/SvcManager' 2 | import express from 'express' 3 | import path from 'path' 4 | 5 | module.exports = function() { 6 | ///////////////////////////////////////////////////////// 7 | // 8 | // 9 | ///////////////////////////////////////////////////////// 10 | const router = express.Router() 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | // POST /igml/:filename 14 | // Post IndoorGML file 15 | // 16 | ///////////////////////////////////////////////////////////////////////////// 17 | 18 | router.post('/', async (req, res) => { 19 | try { 20 | var payload = req.body 21 | 22 | var socketSvc = ServiceManager.getService( 23 | 'SocketSvc') 24 | 25 | socketSvc.broadcast( 'ijson.response', payload ) 26 | 27 | res.json(); 28 | 29 | } catch (ex) { 30 | res.status(ex.statusCode || 500) 31 | res.json(ex) 32 | } 33 | }) 34 | 35 | return router 36 | } 37 | -------------------------------------------------------------------------------- /server/main.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import path from 'path' 3 | import webpack from 'webpack' 4 | import logger from '../build/lib/logger' 5 | import webpackConfig from '../build/webpack.config' 6 | import project from '../project.config' 7 | import compress from 'compression' 8 | import session from 'express-session' 9 | import bodyParser from 'body-parser' 10 | import cors from 'cors' 11 | 12 | import IGMLAPI from './api/iGml' 13 | import IJSONAPI from './api/iJson' 14 | 15 | //Services 16 | import ServiceManager from './services/SvcManager' 17 | import UploadSvc from './services/UploadSvc' 18 | import SocketSvc from './services/SocketSvc' 19 | 20 | const app = express() 21 | app.use(compress()) 22 | 23 | // ------------------------------------ 24 | // Apply Webpack HMR Middleware 25 | // ------------------------------------ 26 | if (project.env === 'development') { 27 | const compiler = webpack(webpackConfig) 28 | 29 | app.use(cors()); 30 | 31 | logger.info('Enabling webpack development and HMR middleware') 32 | app.use(require('webpack-dev-middleware')(compiler, { 33 | publicPath : webpackConfig.output.publicPath, 34 | contentBase : path.resolve(project.basePath, project.srcDir), 35 | hot : true, 36 | quiet : false, 37 | noInfo : false, 38 | lazy : false, 39 | stats : 'normal', 40 | })) 41 | app.use(require('webpack-hot-middleware')(compiler, { 42 | path: '/__webpack_hmr' 43 | })) 44 | 45 | // Serve static assets from ~/public since Webpack is unaware of 46 | // these files. This middleware doesn't need to be enabled outside 47 | // of development since this directory will be copied into ~/dist 48 | // when the application is compiled. 49 | app.use(express.static(path.resolve(project.basePath, 'public'))) 50 | 51 | app.use('/resources', express.static(__dirname + '/../resources')) 52 | 53 | // This rewrites all routes requests to the root /index.html file 54 | // (ignoring file requests). If you want to implement universal 55 | // rendering, you'll want to remove this middleware. 56 | /* 57 | app.use('*', function (req, res, next) { 58 | const filename = path.join(compiler.outputPath, 'index.html') 59 | compiler.outputFileSystem.readFile(filename, (err, result) => { 60 | if (err) { 61 | return next(err) 62 | } 63 | res.set('content-type', 'text/html') 64 | res.send(result) 65 | res.end() 66 | }) 67 | }) 68 | } else { 69 | logger.warn( 70 | 'Server is being run outside of live development mode, meaning it will ' + 71 | 'only serve the compiled application bundle in ~/dist. Generally you ' + 72 | 'do not need an application server for this and can instead use a web ' + 73 | 'server such as nginx to serve your static files. See the "deployment" ' + 74 | 'section in the README for more information on deployment strategies.' 75 | ) 76 | */ 77 | // Serving ~/dist by default. Ideally these files should be served by 78 | // the web server and not the app server, but this helps to demo the 79 | // server in production. 80 | app.use(express.static(path.resolve(project.basePath, project.outDir))) 81 | 82 | app.use('/resources', express.static(__dirname + '/../../resources')) 83 | } 84 | 85 | app.use(bodyParser.urlencoded({ extended: false })) 86 | 87 | //app.use(bodyParser.json()) 88 | app.use(bodyParser.json({limit: '1gb'})); 89 | app.use(bodyParser.raw({limit: '1gb' })); 90 | app.use(bodyParser.text({limit: '1gb'})); 91 | 92 | /////////////////////////////////////////////////////////// 93 | // Services setup 94 | // 95 | /////////////////////////////////////////////////////////// 96 | const uploadSvc = new UploadSvc({ 97 | tempStorage: path.join(__dirname, '../TMP') 98 | }) 99 | ServiceManager.registerService(uploadSvc) 100 | 101 | ///////////////////////////////////////////////////////////////////// 102 | // API Routes setup 103 | // 104 | ///////////////////////////////////////////////////////////////////// 105 | app.use('/api/igml', IGMLAPI()) 106 | app.use('/api/ijson', IJSONAPI()) 107 | 108 | ///////////////////////////////////////////////////////////////////// 109 | // Server Configuration 110 | // 111 | ///////////////////////////////////////////////////////////////////// 112 | ///////////////////////////////////////////////////////////////////// 113 | // 114 | // 115 | ///////////////////////////////////////////////////////////////////// 116 | const runServer = () => { 117 | 118 | try { 119 | 120 | process.on('exit', () => { 121 | 122 | }) 123 | 124 | process.on('uncaughtException', (err) => { 125 | 126 | console.log('uncaughtException') 127 | console.log(err) 128 | console.error(err.stack) 129 | }) 130 | 131 | process.on('unhandledRejection', (reason, p) => { 132 | 133 | console.log('Unhandled Rejection at: Promise ', p, 134 | ' reason: ', reason) 135 | }) 136 | 137 | const server = app.listen( 138 | process.env.PORT || 3000, () => { 139 | 140 | const socketSvc = new SocketSvc({ 141 | session, 142 | server 143 | }) 144 | 145 | ServiceManager.registerService(socketSvc) 146 | 147 | const port = server.address().port 148 | 149 | console.log('Server listening on PORT: ' + port) 150 | console.log('ENV: ' + process.env.NODE_ENV) 151 | }) 152 | 153 | } catch (ex) { 154 | 155 | console.log('Failed to run server... ') 156 | console.log(ex) 157 | } 158 | } 159 | 160 | module.exports = runServer 161 | -------------------------------------------------------------------------------- /server/services/BaseSvc.js: -------------------------------------------------------------------------------- 1 | import ServiceManager from './SvcManager' 2 | import EventEmitter from 'events' 3 | 4 | export default class BaseSvc extends EventEmitter { 5 | 6 | ///////////////////////////////////////////////////////////////// 7 | // 8 | // 9 | ///////////////////////////////////////////////////////////////// 10 | constructor(config = {}) { 11 | 12 | super() 13 | 14 | this._config = config 15 | } 16 | 17 | ///////////////////////////////////////////////////////////////// 18 | // 19 | // 20 | ///////////////////////////////////////////////////////////////// 21 | name() { 22 | 23 | return this._name 24 | } 25 | 26 | ///////////////////////////////////////////////////////////////// 27 | // 28 | // 29 | ///////////////////////////////////////////////////////////////// 30 | config() { 31 | 32 | return this._config 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/services/SocketSvc.js: -------------------------------------------------------------------------------- 1 | import autobind from 'autobind-decorator' 2 | import BaseSvc from './BaseSvc' 3 | import io from 'socket.io' 4 | 5 | export default class SocketSvc extends BaseSvc { 6 | 7 | ///////////////////////////////////////////////////////// 8 | // 9 | // 10 | ///////////////////////////////////////////////////////// 11 | constructor (config) { 12 | 13 | super (config) 14 | 15 | this.io = io(config.server) 16 | 17 | this.connections = {} 18 | 19 | this.handleConnection = this.handleConnection.bind(this); 20 | this.handleDisconnection = this.handleDisconnection.bind(this); 21 | 22 | this.io.sockets.on( 23 | 'connection', 24 | this.handleConnection) 25 | } 26 | 27 | ///////////////////////////////////////////////////////// 28 | // 29 | // 30 | ///////////////////////////////////////////////////////// 31 | name() { 32 | 33 | return 'SocketSvc' 34 | } 35 | 36 | ///////////////////////////////////////////////////////// 37 | // Socket Connection handler 38 | // 39 | ///////////////////////////////////////////////////////// 40 | handleConnection (socket) { 41 | 42 | this.connections[socket.id] = socket 43 | 44 | socket.on('disconnect', () => { 45 | 46 | this.handleDisconnection(socket.id) 47 | }) 48 | 49 | socket.on('broadcast', (data) => { 50 | 51 | const socketIds = Object.keys(this.connections) 52 | 53 | const filter = socketIds.filter((socketId) => { 54 | 55 | return socketId !== socket.id 56 | }) 57 | 58 | this.broadcast(data.msgId, data.msg, filter) 59 | }) 60 | 61 | this.emit('socket.connected', { 62 | id: socket.id 63 | }) 64 | 65 | console.log('Socket connected: ' + socket.id) 66 | } 67 | 68 | ///////////////////////////////////////////////////////// 69 | // Socket Disconnection handler 70 | // 71 | ///////////////////////////////////////////////////////// 72 | handleDisconnection (id) { 73 | 74 | this.emit('socket.disconnected', { 75 | id: id 76 | }) 77 | 78 | if (this.connections[id]) { 79 | 80 | delete this.connections[id] 81 | 82 | console.log('Socket disconnected: ' + id) 83 | } 84 | } 85 | 86 | ///////////////////////////////////////////////////////// 87 | // filter: array of socketIds to broadcast 88 | // If null, broadcast to every connected socket 89 | // 90 | ///////////////////////////////////////////////////////// 91 | broadcast (msgId, msg, filter = null) { 92 | 93 | if (filter) { 94 | 95 | filter = Array.isArray(filter) ? filter : [filter] 96 | 97 | filter.forEach((socketId) => { 98 | 99 | if (this.connections[socketId]){ 100 | 101 | var socket = this.connections[socketId] 102 | 103 | socket.emit(msgId, msg) 104 | } 105 | }) 106 | 107 | } else { 108 | 109 | for (var socketId in this.connections) { 110 | 111 | var socket = this.connections[socketId] 112 | 113 | socket.emit(msgId, msg) 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /server/services/SvcManager.js: -------------------------------------------------------------------------------- 1 | import Events from 'events' 2 | 3 | class SvcManager extends Events.EventEmitter { 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | // 7 | // 8 | ///////////////////////////////////////////////////////////////// 9 | constructor() { 10 | 11 | super() 12 | 13 | this._services = {} 14 | } 15 | 16 | ///////////////////////////////////////////////////////////////// 17 | // 18 | // 19 | ///////////////////////////////////////////////////////////////// 20 | registerService(svc) { 21 | 22 | this._services[svc.name()] = svc 23 | 24 | this.emit('service.register', svc) 25 | } 26 | 27 | ///////////////////////////////////////////////////////////////// 28 | // 29 | // 30 | ///////////////////////////////////////////////////////////////// 31 | getService(name) { 32 | 33 | if (this._services[name]) { 34 | 35 | return this._services[name] 36 | } 37 | 38 | return null 39 | } 40 | } 41 | 42 | const TheSvcManager = new SvcManager() 43 | 44 | export default TheSvcManager 45 | -------------------------------------------------------------------------------- /server/services/UploadSvc.js: -------------------------------------------------------------------------------- 1 | import BaseSvc from './BaseSvc' 2 | import multer from 'multer' 3 | import rimraf from 'rimraf' 4 | import crypto from 'crypto' 5 | import path from 'path' 6 | import fs from 'fs' 7 | 8 | export default class UploadSvc extends BaseSvc { 9 | 10 | ///////////////////////////////////////////////////////// 11 | // 12 | // 13 | ///////////////////////////////////////////////////////// 14 | constructor(config) { 15 | 16 | super(config) 17 | 18 | // Initialize upload 19 | const storage = multer.diskStorage({ 20 | 21 | destination: config.tempStorage, 22 | filename: (req, file, cb) => { 23 | crypto.pseudoRandomBytes(16, (err, raw) => { 24 | if (err) return cb(err) 25 | cb(null, raw.toString('hex') + path.extname( 26 | file.originalname)) 27 | }) 28 | } 29 | }) 30 | 31 | this.multer = multer({storage: storage}) 32 | 33 | // start cleanup task to remove uploaded temp files 34 | setInterval(() => { 35 | this.clean(config.tempStorage, 60 * 60) 36 | }, 1000 * 60 * 60) 37 | 38 | setTimeout(() => { 39 | this.clean(config.tempStorage) 40 | }, 5 * 1000) 41 | } 42 | 43 | ///////////////////////////////////////////////////////// 44 | // 45 | // 46 | ///////////////////////////////////////////////////////// 47 | name() { 48 | 49 | return 'UploadSvc' 50 | } 51 | 52 | ///////////////////////////////////////////////////////// 53 | // 54 | // 55 | ///////////////////////////////////////////////////////// 56 | get uploader () { 57 | 58 | return this.multer 59 | } 60 | 61 | ///////////////////////////////////////////////////////// 62 | // 63 | // 64 | ///////////////////////////////////////////////////////// 65 | clean (dir, maxAge = 0) { 66 | 67 | console.log(`Cleaning Dir: ${dir}`) 68 | 69 | fs.readdir(dir, (err, files) => { 70 | 71 | if (err) { 72 | return console.error(err) 73 | } 74 | 75 | files.forEach((file) => { 76 | 77 | const filePath = path.join(dir, file) 78 | 79 | fs.stat(filePath, (err, stat) => { 80 | 81 | if (err) { 82 | return console.error(err) 83 | } 84 | 85 | const now = new Date() 86 | 87 | const age = (now - new Date(stat.ctime)) / 1000 88 | 89 | if (age > maxAge) { 90 | 91 | return rimraf(filePath, (err) => { 92 | 93 | if (err) { 94 | return console.error(err); 95 | } 96 | }) 97 | } 98 | }) 99 | }) 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/components/BaseComponent/BaseComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class BaseComponent extends React.Component { 4 | 5 | constructor(props) { 6 | super(props) 7 | } 8 | 9 | assignState (state) { 10 | return new Promise((resolve) => { 11 | const newState = Object.assign({}, this.state, state) 12 | this.setState(newState, () => { 13 | resolve() 14 | }) 15 | }) 16 | } 17 | } 18 | 19 | export default BaseComponent 20 | -------------------------------------------------------------------------------- /src/components/BaseComponent/index.js: -------------------------------------------------------------------------------- 1 | import BaseComponent from './BaseComponent' 2 | 3 | export default BaseComponent 4 | 5 | -------------------------------------------------------------------------------- /src/components/ClientAPI/ClientAPI.js: -------------------------------------------------------------------------------- 1 | import superAgent from 'superagent' 2 | 3 | export default class ClientAPI { 4 | 5 | ///////////////////////////////////////////////////////////// 6 | // constructor 7 | // 8 | ///////////////////////////////////////////////////////////// 9 | constructor (apiUrl) { 10 | 11 | this.apiUrl = apiUrl 12 | } 13 | 14 | ///////////////////////////////////////////////////////////// 15 | // 16 | // 17 | ///////////////////////////////////////////////////////////// 18 | buildURL (url = '') { 19 | 20 | return this.apiUrl + 21 | (url.indexOf('/') === 0 ? url:`/${url}`) 22 | } 23 | 24 | ///////////////////////////////////////////////////////////// 25 | // 26 | // 27 | ///////////////////////////////////////////////////////////// 28 | buildParams (params) { 29 | 30 | const defaultParams = { 31 | headers: { 32 | 'Content-Type': 'application/json', 33 | 'Accept': 'application/json' 34 | }, 35 | type: 'GET', 36 | data: null 37 | } 38 | 39 | return Object.assign({}, defaultParams, 40 | params, { 41 | url: this.buildURL(params.url) 42 | }) 43 | } 44 | 45 | ///////////////////////////////////////////////////////////// 46 | // fetch wrapper 47 | // 48 | ///////////////////////////////////////////////////////////// 49 | fetch (url, params) { 50 | 51 | return fetch(this.buildURL(url), params).then(response => { 52 | 53 | return response.json().then(json => { 54 | 55 | return response.ok ? json : Promise.reject(json); 56 | }) 57 | }) 58 | } 59 | 60 | ///////////////////////////////////////////////////////////// 61 | // $.ajax wrapper 62 | // 63 | ///////////////////////////////////////////////////////////// 64 | ajax (paramsOrUrl) { 65 | 66 | const params = (typeof paramsOrUrl === 'object') 67 | ? this.buildParams(paramsOrUrl) 68 | : { 69 | url: this.buildURL(paramsOrUrl), 70 | type: 'GET', 71 | data: null 72 | } 73 | 74 | return new Promise((resolve, reject) => { 75 | 76 | Object.assign(params, { 77 | success: (response) => { 78 | 79 | return (params.rawBody && response.body) 80 | ? resolve (response.body) 81 | : resolve(response) 82 | }, 83 | error: function (error) { 84 | 85 | reject(error) 86 | } 87 | }) 88 | 89 | $.ajax(params) 90 | }) 91 | } 92 | 93 | ///////////////////////////////////////////////////////////// 94 | // 95 | // 96 | ///////////////////////////////////////////////////////////// 97 | upload (url, file, opts = {}) { 98 | 99 | return new Promise ((resolve, reject) => { 100 | 101 | const req = superAgent.post(this.buildURL(url)) 102 | 103 | req.on('progress', (e) => { 104 | 105 | if (opts.progress) { 106 | 107 | opts.progress(e.percent) 108 | } 109 | }) 110 | 111 | req.attach(opts.tag || 'file', file) 112 | 113 | if (opts.data) { 114 | 115 | for (var key in opts.data) { 116 | 117 | req.field(key, opts.data[key]) 118 | } 119 | } 120 | 121 | req.end((err, response) => { 122 | 123 | if (err) { 124 | 125 | return reject (err) 126 | } 127 | 128 | resolve (response) 129 | }) 130 | }) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/components/ClientAPI/index.js: -------------------------------------------------------------------------------- 1 | import ClientAPI from './ClientAPI' 2 | 3 | export default ClientAPI 4 | -------------------------------------------------------------------------------- /src/components/Dialogs/AboutDlg/AboutDlg.js: -------------------------------------------------------------------------------- 1 | import ServiceManager from 'SvcManager' 2 | import PropTypes from 'prop-types' 3 | import Modal from 'react-modal' 4 | import React from 'react' 5 | import './AboutDlg.scss' 6 | 7 | export default class AboutDlg extends React.Component { 8 | 9 | ///////////////////////////////////////////////////////// 10 | // 11 | // 12 | ///////////////////////////////////////////////////////// 13 | constructor() { 14 | 15 | super() 16 | } 17 | 18 | ///////////////////////////////////////////////////////// 19 | // 20 | // 21 | ///////////////////////////////////////////////////////// 22 | componentDidMount() { 23 | 24 | this.forgeSvc = ServiceManager.getService('ForgeSvc') 25 | } 26 | 27 | ///////////////////////////////////////////////////////// 28 | // 29 | // 30 | ///////////////////////////////////////////////////////// 31 | close () { 32 | 33 | this.props.close() 34 | } 35 | 36 | ///////////////////////////////////////////////////////// 37 | // 38 | // 39 | ///////////////////////////////////////////////////////// 40 | render() { 41 | 42 | const clientId = this.forgeSvc 43 | ? this.forgeSvc.clientId 44 | : '' 45 | 46 | return ( 47 |
48 | {this.close()}}> 52 | 53 |
54 | 55 | About Forge RCDB ... 56 |
57 | 58 |
59 |
60 | Forge / Responsive / Connected / Database 61 |
62 | Written by Philippe Leefsma 63 |
64 | 65 | @F3lipek 66 | 67 |  - October 2016 68 |
69 |
70 |
71 | This App ClientID: 72 |
73 | {clientId} 74 |
75 |
76 | Source on github: 77 |
78 | 79 | Forge RCDB 80 | 81 |
82 |
83 | 84 |
85 |
86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/Dialogs/AboutDlg/AboutDlg.scss: -------------------------------------------------------------------------------- 1 | 2 | .dialog.about { 3 | left: calc(50% - 170px); 4 | top: calc(40% - 100px); 5 | height: 234px; 6 | width: 340px; 7 | 8 | .clientId { 9 | margin: 4px 14px 14px 14px; 10 | border: 1px solid #9a9a9a; 11 | background: #ececec; 12 | border-radius: 4px; 13 | user-select: text; 14 | cursor: text; 15 | padding: 2px; 16 | } 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/Dialogs/AboutDlg/index.js: -------------------------------------------------------------------------------- 1 | import AboutDlg from './AboutDlg' 2 | 3 | export default AboutDlg 4 | -------------------------------------------------------------------------------- /src/components/Dialogs/DatabaseDlg/DatabaseDlg.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import Modal from 'react-modal' 3 | import './DatabaseDlg.scss' 4 | import React from 'react' 5 | 6 | export default class DatabaseDlg extends React.Component { 7 | 8 | ///////////////////////////////////////////////////////////////// 9 | // 10 | // 11 | ///////////////////////////////////////////////////////////////// 12 | constructor() { 13 | 14 | super() 15 | 16 | this.items = [ 17 | 18 | ] 19 | } 20 | 21 | ///////////////////////////////////////////////////////////////// 22 | // 23 | // 24 | ///////////////////////////////////////////////////////////////// 25 | close () { 26 | 27 | this.props.close() 28 | } 29 | 30 | ///////////////////////////////////////////////////////////////// 31 | // 32 | // 33 | ///////////////////////////////////////////////////////////////// 34 | onClick (item) { 35 | 36 | this.props.onSelectItem(item) 37 | this.props.close() 38 | } 39 | 40 | ///////////////////////////////////////////////////////////////// 41 | // 42 | // 43 | ///////////////////////////////////////////////////////////////// 44 | render() { 45 | 46 | return ( 47 |
48 | {this.close()}}> 52 | 53 |
54 | 55 | Select Database ... 56 |
57 | 58 |
59 | 60 | NOT YET IMPLEMENTED :( ... 61 | 62 | {this.items.map((item) => { 63 | return ( 64 | {this.onClick(item)}}> 65 |
66 | 67 |
68 | {item.caption} 69 |
70 |
71 |
) 72 | }) 73 | } 74 |
75 | 76 |
77 |
78 | ) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/Dialogs/DatabaseDlg/DatabaseDlg.scss: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 260px) { 2 | 3 | .dialog.database { 4 | left: calc(50% - 125px); 5 | top: calc(40% - 100px); 6 | height: 174px; 7 | width: 250px; 8 | } 9 | 10 | .database .responsive-grid a { 11 | text-decoration: none; 12 | display: inline-block; 13 | margin-bottom: 4px; 14 | margin-right: 4px; 15 | margin-left: 4px; 16 | color: #606060; 17 | width: 100px; 18 | } 19 | 20 | .database .responsive-grid img { 21 | transition: transform .2s ease-in-out; 22 | background: #ccc; 23 | max-width: 100%; 24 | display: block; 25 | height: auto; 26 | border: none; 27 | width: 90px; 28 | height: 82px; 29 | margin: 4px; 30 | } 31 | } 32 | 33 | @media screen and (max-width: 259px) { 34 | 35 | .dialog.database { 36 | left: calc(50% - 175px); 37 | top: calc(40% - 100px); 38 | height: 188px; 39 | width: 200px; 40 | } 41 | 42 | .database .responsive-grid a { 43 | text-decoration: none; 44 | display: inline-block; 45 | margin-bottom: 4px; 46 | margin-right: 4px; 47 | margin-left: 4px; 48 | color: #606060; 49 | width: 100px; 50 | } 51 | 52 | .database .responsive-grid img { 53 | transition: transform .2s ease-in-out; 54 | background: #ccc; 55 | max-width: 100%; 56 | display: block; 57 | height: auto; 58 | border: none; 59 | width: 90px; 60 | height: 82px; 61 | margin: 4px; 62 | } 63 | } 64 | 65 | .database .responsive-grid { 66 | height: calc(100% - 40px); 67 | justify-content: center; 68 | flex-wrap: wrap; 69 | display: flex; 70 | } 71 | 72 | .database .responsive-grid a:nth-of-type(2n) { 73 | margin-right: 0; 74 | } 75 | 76 | .database .responsive-grid a:hover img { 77 | transform: scale(1.10); 78 | } 79 | 80 | .database .responsive-grid figure { 81 | background-color: #e0e0e0; 82 | border: 1px solid #d2d2d2; 83 | border-radius: 4px; 84 | overflow: hidden; 85 | margin: 0; 86 | } 87 | 88 | .database .responsive-grid figcaption { 89 | margin: 4px; 90 | } 91 | -------------------------------------------------------------------------------- /src/components/Dialogs/DatabaseDlg/index.js: -------------------------------------------------------------------------------- 1 | import DatabaseDlg from './DatabaseDlg' 2 | 3 | export default DatabaseDlg 4 | -------------------------------------------------------------------------------- /src/components/Dialogs/LayoutDlg/LayoutDlg.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import Modal from 'react-modal' 3 | import './LayoutDlg.scss' 4 | import React from 'react' 5 | 6 | export default class LayoutDlg extends React.Component { 7 | 8 | ///////////////////////////////////////////////////////////////// 9 | // 10 | // 11 | ///////////////////////////////////////////////////////////////// 12 | constructor() { 13 | 14 | super() 15 | 16 | this.items = [ 17 | { 18 | className: 'split-layout-left', 19 | layoutType: 'flexLayoutLeft', 20 | caption: 'Split Layout - Left', 21 | key: '1' 22 | }, 23 | { 24 | className: 'split-layout-right', 25 | layoutType: 'flexLayoutRight', 26 | caption: 'Split Layout - Right', 27 | key: '2' 28 | }, 29 | { 30 | className: 'grid-layout', 31 | layoutType: 'gridLayout', 32 | caption: 'Grid Layout', 33 | key: '3' 34 | } 35 | ] 36 | } 37 | 38 | ///////////////////////////////////////////////////////////////// 39 | // 40 | // 41 | ///////////////////////////////////////////////////////////////// 42 | close () { 43 | 44 | this.props.close() 45 | } 46 | 47 | ///////////////////////////////////////////////////////////////// 48 | // 49 | // 50 | ///////////////////////////////////////////////////////////////// 51 | onClick (item) { 52 | 53 | this.props.layoutChange(item.layoutType) 54 | this.props.saveAppState() 55 | this.props.close() 56 | } 57 | 58 | ///////////////////////////////////////////////////////////////// 59 | // 60 | // 61 | ///////////////////////////////////////////////////////////////// 62 | render() { 63 | 64 | return ( 65 |
66 | {this.close()}}> 70 | 71 |
72 | 73 | Select layout type ... 74 |
75 | 76 |
77 | 78 | {this.items.map((item) => { 79 | return ( 80 | {this.onClick(item)}}> 81 |
82 | 83 |
84 | {item.caption} 85 |
86 |
87 |
) 88 | }) 89 | } 90 |
91 | 92 |
93 |
94 | ) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/components/Dialogs/LayoutDlg/index.js: -------------------------------------------------------------------------------- 1 | import LayoutDlg from './LayoutDlg' 2 | 3 | export default LayoutDlg 4 | -------------------------------------------------------------------------------- /src/components/Dialogs/ServiceDlg/ServiceDlg.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import Modal from 'react-modal' 3 | import './ServiceDlg.scss' 4 | import React from 'react' 5 | 6 | export default class ServiceDlg extends React.Component { 7 | 8 | ///////////////////////////////////////////////////////// 9 | // 10 | // 11 | ///////////////////////////////////////////////////////// 12 | static propTypes = { 13 | className: PropTypes.string 14 | } 15 | 16 | ///////////////////////////////////////////////////////// 17 | // 18 | // 19 | ///////////////////////////////////////////////////////// 20 | static defaultProps = { 21 | 22 | captionCancel: 'Cancel', 23 | captionOK: 'OK', 24 | 25 | showCancel: true, 26 | showOK: true, 27 | 28 | disableOK: false, 29 | className: '' 30 | } 31 | 32 | ///////////////////////////////////////////////////////// 33 | // 34 | // 35 | ///////////////////////////////////////////////////////// 36 | constructor() { 37 | 38 | super () 39 | 40 | this.onRequestClose = this.onRequestClose.bind(this) 41 | } 42 | 43 | ///////////////////////////////////////////////////////// 44 | // 45 | // 46 | ///////////////////////////////////////////////////////// 47 | onOk () { 48 | 49 | this.props.close('OK') 50 | } 51 | 52 | ///////////////////////////////////////////////////////// 53 | // 54 | // 55 | ///////////////////////////////////////////////////////// 56 | onCancel () { 57 | 58 | this.props.close('CANCEL') 59 | } 60 | 61 | ///////////////////////////////////////////////////////// 62 | // 63 | // 64 | ///////////////////////////////////////////////////////// 65 | close () { 66 | 67 | this.props.close('CANCEL') 68 | } 69 | 70 | ///////////////////////////////////////////////////////// 71 | // 72 | // 73 | ///////////////////////////////////////////////////////// 74 | renderTitle () { 75 | 76 | if (this.props.renderTitle) { 77 | 78 | return this.props.renderTitle() 79 | } 80 | 81 | return ( 82 |
83 | 84 | {this.props.title} 85 |
86 | ) 87 | } 88 | 89 | ///////////////////////////////////////////////////////// 90 | // 91 | // 92 | ///////////////////////////////////////////////////////// 93 | renderContent () { 94 | 95 | if (this.props.renderContent) { 96 | 97 | return this.props.renderContent() 98 | } 99 | 100 | return ( 101 |
102 | {this.props.content} 103 |
104 | ) 105 | } 106 | 107 | ///////////////////////////////////////////////////////// 108 | // 109 | // 110 | ///////////////////////////////////////////////////////// 111 | onRequestClose () { 112 | 113 | if (this.props.onRequestClose) { 114 | 115 | const close = () => this.close () 116 | 117 | return this.props.onRequestClose(close) 118 | } 119 | 120 | this.close () 121 | } 122 | 123 | ///////////////////////////////////////////////////////// 124 | // 125 | // 126 | ///////////////////////////////////////////////////////// 127 | renderControls () { 128 | 129 | if (this.props.renderControls) { 130 | 131 | return this.props.renderControls() 132 | } 133 | 134 | return ( 135 |
136 | { 137 | this.props.showOK && 138 | 146 | } 147 | { 148 | this.props.showCancel && 149 | 156 | } 157 |
158 | ) 159 | } 160 | 161 | ///////////////////////////////////////////////////////// 162 | // 163 | // 164 | ///////////////////////////////////////////////////////// 165 | render() { 166 | 167 | const classNames = [ 168 | 'dialog', 'service', 169 | ...this.props.className.split(' ') 170 | ] 171 | 172 | return ( 173 |
174 | 178 | 179 | {this.renderTitle()} 180 | {this.renderContent()} 181 | {this.renderControls()} 182 | 183 | 184 |
185 | ) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/components/Dialogs/ServiceDlg/ServiceDlg.scss: -------------------------------------------------------------------------------- 1 | 2 | .dialog.service { 3 | height: auto !important; 4 | left: calc(50% - 140px); 5 | top: calc(40% - 100px); 6 | width: 280px; 7 | } 8 | 9 | .dialog.service > .controls { 10 | border-top: 1px solid #d2d2d2; 11 | top: calc(100% - 114px); 12 | padding: 6px 6px 0 0; 13 | position: relative; 14 | height: 40px; 15 | width: 100%; 16 | } 17 | 18 | .dialog.service > .controls > button { 19 | background: whitesmoke; 20 | border-radius: 4px; 21 | margin-left: 4px; 22 | min-width: 84px; 23 | outline: none; 24 | float: right; 25 | height: 26px; 26 | } 27 | 28 | .dialog.service > .controls > button > span { 29 | position: relative; 30 | margin: 0 4px 0 0; 31 | font-size: 20px; 32 | color: #9b9b9b; 33 | top: 0px; 34 | } 35 | 36 | .dialog.service > .controls > button:hover:not(:disabled) > span { 37 | color: #FF9800; 38 | } 39 | 40 | .dialog.service > .controls > button:disabled > span { 41 | color: #d0d0d0; 42 | } 43 | 44 | .dialog.service > .controls > button:disabled > label { 45 | pointer-events: none; 46 | color: #9b9b9b; 47 | } 48 | 49 | .dialog.service > .controls > button > label { 50 | font-weight: normal; 51 | position: relative; 52 | top: -2px; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/Dialogs/ServiceDlg/index.js: -------------------------------------------------------------------------------- 1 | import ServiceDlg from './ServiceDlg' 2 | 3 | export default ServiceDlg 4 | -------------------------------------------------------------------------------- /src/components/Dialogs/ThemeDlg/ThemeDlg.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import Modal from 'react-modal' 3 | import React from 'react' 4 | import './ThemeDlg.scss' 5 | 6 | export default class ThemeDlg extends React.Component { 7 | 8 | ///////////////////////////////////////////////////////////////// 9 | // 10 | // 11 | ///////////////////////////////////////////////////////////////// 12 | constructor() { 13 | 14 | super() 15 | 16 | this.items = [ 17 | { 18 | css: '/resources/themes/forge.css', 19 | className: 'forge-theme', 20 | name: 'forge-theme', 21 | caption: 'Forge', 22 | viewer: { 23 | backgroundColor: [ 24 | 255, 226, 110, 25 | 219, 219, 219 26 | ] 27 | }, 28 | key: '1' 29 | }, 30 | { 31 | css: '/resources/themes/snow-white.css', 32 | className: 'snow-theme', 33 | name: 'snow-white-theme', 34 | caption: 'Snow', 35 | viewer: { 36 | backgroundColor: [ 37 | 245, 245, 245, 38 | 245, 245, 245 39 | ] 40 | }, 41 | key: '2' 42 | } 43 | ] 44 | } 45 | 46 | ///////////////////////////////////////////////////////////////// 47 | // 48 | // 49 | ///////////////////////////////////////////////////////////////// 50 | close () { 51 | 52 | this.props.close() 53 | } 54 | 55 | ///////////////////////////////////////////////////////////////// 56 | // 57 | // 58 | ///////////////////////////////////////////////////////////////// 59 | onClick (item) { 60 | 61 | this.props.themeChange(item) 62 | this.props.saveAppState() 63 | this.props.close() 64 | } 65 | 66 | ///////////////////////////////////////////////////////////////// 67 | // 68 | // 69 | ///////////////////////////////////////////////////////////////// 70 | render() { 71 | 72 | return ( 73 |
74 | {this.close()}}> 78 | 79 |
80 | 81 | Select theme ... 82 |
83 | 84 |
85 | 86 | {this.items.map((item) => { 87 | return ( 88 | {this.onClick(item)}}> 89 |
90 | 91 |
92 | {item.caption} 93 |
94 |
95 |
) 96 | }) 97 | } 98 |
99 | 100 |
101 |
102 | ) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/components/Dialogs/ThemeDlg/ThemeDlg.scss: -------------------------------------------------------------------------------- 1 | 2 | @media screen and (min-width: 260px) { 3 | 4 | .dialog.theme { 5 | left: calc(50% - 125px); 6 | top: calc(40% - 100px); 7 | height: 174px; 8 | width: 250px; 9 | } 10 | 11 | .theme .responsive-grid a { 12 | text-decoration: none; 13 | display: inline-block; 14 | margin-bottom: 4px; 15 | margin-right: 4px; 16 | margin-left: 4px; 17 | color: #606060; 18 | width: 100px; 19 | } 20 | 21 | .theme .responsive-grid img { 22 | transition: transform .2s ease-in-out; 23 | max-width: 100%; 24 | display: block; 25 | height: auto; 26 | border: none; 27 | width: 90px; 28 | height: 82px; 29 | margin: 4px; 30 | } 31 | } 32 | 33 | @media screen and (max-width: 259px) { 34 | 35 | .dialog.theme { 36 | left: calc(50% - 175px); 37 | top: calc(40% - 100px); 38 | height: 188px; 39 | width: 200px; 40 | } 41 | 42 | .theme .responsive-grid a { 43 | text-decoration: none; 44 | display: inline-block; 45 | margin-bottom: 4px; 46 | margin-right: 4px; 47 | margin-left: 4px; 48 | color: #606060; 49 | width: 100px; 50 | } 51 | 52 | .theme .responsive-grid img { 53 | transition: transform .2s ease-in-out; 54 | max-width: 100%; 55 | display: block; 56 | height: auto; 57 | border: none; 58 | width: 90px; 59 | height: 82px; 60 | margin: 4px; 61 | } 62 | } 63 | 64 | .theme .responsive-grid { 65 | height: calc(100% - 40px); 66 | justify-content: center; 67 | flex-wrap: wrap; 68 | display: flex; 69 | } 70 | 71 | .theme .responsive-grid a:nth-of-type(2n) { 72 | margin-right: 0; 73 | } 74 | 75 | .theme .responsive-grid a:hover img { 76 | transform: scale(1.10); 77 | } 78 | 79 | .theme .responsive-grid figure { 80 | background-color: #e0e0e0; 81 | border: 1px solid #d2d2d2; 82 | border-radius: 4px; 83 | overflow: hidden; 84 | margin: 0; 85 | } 86 | 87 | .theme .responsive-grid figcaption { 88 | margin: 4px; 89 | } 90 | 91 | .forge-theme { 92 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffd65e+0,fcde8a+100 */ 93 | background: #ffd65e; /* Old browsers */ 94 | background: -moz-linear-gradient(top, #ffd65e 0%, #fcde8a 100%); /* FF3.6-15 */ 95 | background: -webkit-linear-gradient(top, #ffd65e 0%,#fcde8a 100%); /* Chrome10-25,Safari5.1-6 */ 96 | background: linear-gradient(to bottom, #ffd65e 0%,#fcde8a 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 97 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffd65e', endColorstr='#fcde8a',GradientType=0 ); /* IE6-9 */ 98 | } 99 | 100 | .snow-theme { 101 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffffff+0,f6f6f6+47,ededed+100;White+3D+%231 */ 102 | background: rgb(255,255,255); /* Old browsers */ 103 | background: -moz-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(246,246,246,1) 47%, rgba(237,237,237,1) 100%); /* FF3.6-15 */ 104 | background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(246,246,246,1) 47%,rgba(237,237,237,1) 100%); /* Chrome10-25,Safari5.1-6 */ 105 | background: linear-gradient(to bottom, rgba(255,255,255,1) 0%,rgba(246,246,246,1) 47%,rgba(237,237,237,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 106 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 ); /* IE6-9 */ 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/components/Dialogs/ThemeDlg/index.js: -------------------------------------------------------------------------------- 1 | import ThemeDlg from './ThemeDlg' 2 | 3 | export default ThemeDlg 4 | -------------------------------------------------------------------------------- /src/components/EventsEmitter/EventsEmitter.js: -------------------------------------------------------------------------------- 1 | 2 | export default class EventsEmitter { 3 | 4 | ///////////////////////////////////////////////////////// 5 | // 6 | // 7 | ///////////////////////////////////////////////////////// 8 | constructor () { 9 | 10 | this._events = {} 11 | } 12 | 13 | ///////////////////////////////////////////////////////// 14 | // Supports multiple events space-separated 15 | // 16 | ///////////////////////////////////////////////////////// 17 | on (events, fct) { 18 | 19 | events.split(' ').forEach((event) => { 20 | 21 | this._events[event] = this._events[event] || [] 22 | this._events[event].push(fct) 23 | }) 24 | 25 | return this 26 | } 27 | 28 | ///////////////////////////////////////////////////////// 29 | // Supports multiple events space-separated 30 | // 31 | ///////////////////////////////////////////////////////// 32 | off (events, fct) { 33 | 34 | if(events == undefined){ 35 | this._events = {} 36 | return 37 | } 38 | 39 | events.split(' ').forEach((event) => { 40 | 41 | if (event in this._events === false) 42 | return; 43 | 44 | if (fct) { 45 | 46 | this._events[event].splice( 47 | this._events[event].indexOf(fct), 1) 48 | 49 | } else { 50 | 51 | this._events[event] = [] 52 | } 53 | }) 54 | 55 | return this 56 | } 57 | 58 | ///////////////////////////////////////////////////////// 59 | // 60 | // 61 | ///////////////////////////////////////////////////////// 62 | emit (event /* , args... */) { 63 | 64 | if(this._events[event] === undefined) 65 | return null; 66 | 67 | var tmpArray = this._events[event].slice() 68 | 69 | for (var i = 0; i < tmpArray.length; ++i) { 70 | 71 | var result = tmpArray[i].apply(this, 72 | Array.prototype.slice.call(arguments, 1)); 73 | 74 | if(result !== undefined) { 75 | return result 76 | } 77 | } 78 | } 79 | 80 | ///////////////////////////////////////////////////////// 81 | // 82 | // 83 | ///////////////////////////////////////////////////////// 84 | guid (format = 'xxxxxxxxxxxx') { 85 | 86 | var d = new Date().getTime() 87 | 88 | const guid = format.replace( 89 | /[xy]/g, 90 | function (c) { 91 | var r = (d + Math.random() * 16) % 16 | 0 92 | d = Math.floor(d / 16) 93 | return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16) 94 | }) 95 | 96 | return guid 97 | } 98 | } 99 | 100 | 101 | ///////////////////////////////////////////////////////// 102 | // 103 | // 104 | ///////////////////////////////////////////////////////// 105 | export const EventsEmitterComposer = 106 | (BaseClass) => class extends BaseClass { 107 | 108 | ///////////////////////////////////////////////////////// 109 | // 110 | // 111 | ///////////////////////////////////////////////////////// 112 | constructor (arg1, arg2, arg3, arg4, arg5) { 113 | 114 | super (arg1, arg2, arg3, arg4, arg5) 115 | 116 | this._events = {} 117 | } 118 | 119 | ///////////////////////////////////////////////////////// 120 | // Supports multiple events space-separated 121 | // 122 | ///////////////////////////////////////////////////////// 123 | on (events, fct) { 124 | 125 | events.split(' ').forEach((event) => { 126 | 127 | this._events[event] = this._events[event] || []; 128 | this._events[event].push(fct); 129 | }) 130 | 131 | return this 132 | } 133 | 134 | ///////////////////////////////////////////////////////// 135 | // Supports multiple events space-separated 136 | // 137 | ///////////////////////////////////////////////////////// 138 | off (events, fct) { 139 | 140 | if(events == undefined){ 141 | this._events = {}; 142 | return; 143 | } 144 | 145 | events.split(' ').forEach((event) => { 146 | 147 | if (event in this._events === false) 148 | return; 149 | 150 | if (fct) { 151 | 152 | this._events[event].splice( 153 | this._events[event].indexOf(fct), 1) 154 | 155 | } else { 156 | 157 | this._events[event] = [] 158 | } 159 | }) 160 | 161 | return this 162 | } 163 | 164 | ///////////////////////////////////////////////////////// 165 | // 166 | // 167 | ///////////////////////////////////////////////////////// 168 | emit (event /* , args... */) { 169 | 170 | if(this._events[event] === undefined) 171 | return null; 172 | 173 | var tmpArray = this._events[event].slice() 174 | 175 | for (var i = 0; i < tmpArray.length; ++i) { 176 | 177 | var result = tmpArray[i].apply(this, 178 | Array.prototype.slice.call(arguments, 1)); 179 | 180 | if(result !== undefined) { 181 | return result 182 | } 183 | } 184 | } 185 | 186 | ///////////////////////////////////////////////////////// 187 | // 188 | // 189 | ///////////////////////////////////////////////////////// 190 | guid (format = 'xxxxxxxxxxxx') { 191 | 192 | var d = new Date().getTime() 193 | 194 | const guid = format.replace( 195 | /[xy]/g, 196 | function (c) { 197 | var r = (d + Math.random() * 16) % 16 | 0 198 | d = Math.floor(d / 16) 199 | return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16) 200 | }) 201 | 202 | return guid 203 | } 204 | } 205 | 206 | 207 | -------------------------------------------------------------------------------- /src/components/EventsEmitter/index.js: -------------------------------------------------------------------------------- 1 | import {EventsEmitterComposer} from './EventsEmitter' 2 | import EventsEmitter from './EventsEmitter' 3 | 4 | EventsEmitter.Composer = EventsEmitterComposer 5 | 6 | export default EventsEmitter 7 | -------------------------------------------------------------------------------- /src/components/ExtensionPane/ExtensionPane.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | class ExtensionPane extends React.Component { 5 | 6 | ///////////////////////////////////////////////////////// 7 | // 8 | // 9 | ///////////////////////////////////////////////////////// 10 | static propTypes = { 11 | className: PropTypes.string 12 | } 13 | 14 | ///////////////////////////////////////////////////////// 15 | // 16 | // 17 | ///////////////////////////////////////////////////////// 18 | static defaultProps = { 19 | className: '' 20 | } 21 | 22 | ///////////////////////////////////////////////////////// 23 | // 24 | // 25 | ///////////////////////////////////////////////////////// 26 | render () { 27 | 28 | const classNames = [ 29 | 'extension-pane', 30 | ...this.props.className.split(' ') 31 | ] 32 | 33 | return ( 34 |
35 | {this.props.children} 36 |
37 | ) 38 | } 39 | } 40 | 41 | export default ExtensionPane 42 | -------------------------------------------------------------------------------- /src/components/ExtensionPane/index.js: -------------------------------------------------------------------------------- 1 | import ExtensionPane from './ExtensionPane' 2 | 3 | export default ExtensionPane 4 | -------------------------------------------------------------------------------- /src/components/FileUpload/FileUpload.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Dropzone from 'react-dropzone' 3 | 4 | export default class FileUpload extends React.Component { 5 | 6 | constructor() { 7 | super() 8 | this.state = { 9 | accept: '', 10 | files: [], 11 | dropzoneActive: false 12 | } 13 | } 14 | 15 | onDragEnter() { 16 | console.log('onDragEnter') 17 | this.setState({ 18 | dropzoneActive: true 19 | }); 20 | } 21 | 22 | onDragLeave() { 23 | console.log('onDragLeave') 24 | this.setState({ 25 | dropzoneActive: false 26 | }); 27 | } 28 | 29 | onDrop(files) { 30 | console.log('onDrop') 31 | this.setState({ 32 | files, 33 | dropzoneActive: false 34 | }); 35 | } 36 | 37 | applyMimeTypes(event) { 38 | this.setState({ 39 | accept: event.target.value 40 | }); 41 | } 42 | 43 | render() { 44 | const { accept, files, dropzoneActive } = this.state; 45 | const overlayStyle = { 46 | position: 'absolute', 47 | top: 0, 48 | right: 0, 49 | bottom: 0, 50 | left: 0, 51 | padding: '2.5em 0', 52 | background: 'rgba(0,0,0,0.5)', 53 | textAlign: 'center', 54 | color: '#fff' 55 | }; 56 | 57 | return ( 58 | 65 | { dropzoneActive &&
Drop files...
} 66 |
67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/FileUpload/index.js: -------------------------------------------------------------------------------- 1 | import FileUpload from './FileUpload' 2 | 3 | export default FileUpload 4 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import Menubar from '../Menubar/Menubar' 2 | import React from 'react' 3 | import './Header.scss' 4 | 5 | export default class Header extends React.Component { 6 | 7 | render () { 8 | 9 | return ( 10 |
11 | 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | .route--active { 2 | font-weight: bold; 3 | text-decoration: underline; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | 3 | export default Header 4 | -------------------------------------------------------------------------------- /src/components/IGMLHelper/index.js: -------------------------------------------------------------------------------- 1 | import IGMLHelper from './IGMLHelper' 2 | 3 | export default IGMLHelper 4 | -------------------------------------------------------------------------------- /src/components/IGMLHelper/node.js: -------------------------------------------------------------------------------- 1 | export default class Node { 2 | constructor() { 3 | this.parent; 4 | this.group; 5 | this.children = []; 6 | this.childTrue; 7 | } 8 | 9 | init(group, parent) { 10 | this.parent = parent; 11 | this.group = group; 12 | var children = group.children; 13 | this.childTrue = children.length; 14 | 15 | for (var c of children) { 16 | var child = new node(); 17 | child.init(c, this); 18 | this.children.push(child); 19 | } 20 | } 21 | 22 | setChild(flag) { 23 | this.group.visible = flag; 24 | for (var child of this.children) { 25 | child.setChild(flag); 26 | } 27 | } 28 | 29 | setParent() { 30 | if (this.childTrue == 0) { 31 | this.group.visible = false; 32 | } else { 33 | this.group.visible = true; 34 | } 35 | if (this.parent !== null) { 36 | if (this.group.visible == true) { 37 | this.parent.childTrue++; 38 | } else { 39 | this.parent.childTrue--; 40 | } 41 | this.parent.setParent(); 42 | } 43 | } 44 | 45 | change(group) { 46 | if (this.group == group) { 47 | if (this.parent !== null) { 48 | if (group.visible == true) { 49 | this.parent.childTrue++; 50 | } else { 51 | this.parent.childTrue--; 52 | } 53 | this.parent.setParent(); 54 | } 55 | 56 | for (var child of this.children) { 57 | child.setChild(group.visible); 58 | } 59 | } else if (this.children !== 'undefined') { 60 | for (var child of this.children) { 61 | child.change(group); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/IJSONHelper/index.js: -------------------------------------------------------------------------------- 1 | import IJSONHelper from './IJSONHelper' 2 | 3 | export default IJSONHelper 4 | -------------------------------------------------------------------------------- /src/components/Indoor/CellSpaceBoundary.js: -------------------------------------------------------------------------------- 1 | import Polygon from './Polygon' 2 | import LineString from './LineString' 3 | 4 | export default class CellSpaceBoundary { 5 | constructor() { 6 | this.id 7 | this.name = "" 8 | this.geometry = [] //Polygon array 9 | this.duality = "" 10 | this.geometryType = "3D" 11 | } 12 | 13 | fromJSON(content, parser) { 14 | this.id = content.id 15 | 16 | var n = content.name 17 | if (typeof n !== 'undefined') { 18 | this.name = n[0].value 19 | } 20 | 21 | var du = content.duality; 22 | if (typeof du !== 'undefined') { 23 | this.duality = du.href 24 | } 25 | 26 | var cellSpaceBoundaryGeometry = content.cellSpaceBoundaryGeometry 27 | 28 | var geod = cellSpaceBoundaryGeometry.geometry3D; 29 | if (typeof geod !== 'undefined') { 30 | var exterior = geod.abstractSurface 31 | if (typeof exterior !== 'undefined') { 32 | var polygon = new Polygon() 33 | polygon.fromJSON(exterior.value, parser) 34 | this.geometry.push(polygon); 35 | this.geometryType = "3D" 36 | } 37 | // Xlink 38 | else { 39 | var xlink = geod.abstractSurface.value.baseSurface.href 40 | xlink = xlink.substr(1, xlink.length) 41 | var polygon = parser.gmlIdMap.get(xlink) 42 | this.geometry.push(polygon); 43 | if(polygon == 'undefined') { 44 | parser.placeHolder[xlink] = this.geometry 45 | } 46 | } 47 | //TODO : interior 48 | } else { 49 | geod = cellSpaceBoundaryGeometry.geometry2D 50 | if (typeof geod !== 'undefined') { 51 | var curve = geod.abstractCurve; 52 | if(typeof curve !== 'undefined') { 53 | var linestring = new LineString(); 54 | linestring.fromJSON(curve.value, parser) 55 | this.geometry.push(linestring); 56 | this.geometryType = "2D" 57 | } 58 | } 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/components/Indoor/Cellspace.js: -------------------------------------------------------------------------------- 1 | import Polygon from './Polygon' 2 | 3 | export default class CellSpace { 4 | constructor() { 5 | this.id; 6 | this.name = ""; 7 | this.geometry = []; //Polygon array 8 | this.duality = ""; 9 | this.partialboundedBy; //boundarysurface 10 | } 11 | 12 | fromJSON(content, parser) { 13 | this.id = content.id; 14 | 15 | var n = content.name; 16 | if (typeof n !== 'undefined') { 17 | this.name = n[0].value; 18 | } 19 | 20 | var du = content.duality; 21 | if (typeof du !== 'undefined') { 22 | this.duality = du.href; 23 | } 24 | 25 | var bound = content.partialboundedBy; 26 | if (typeof bound !== 'undefined') { 27 | this.partialboundedBy = bound[0].href; 28 | } 29 | 30 | var cellGeometry = content.cellSpaceGeometry 31 | 32 | var geod = cellGeometry.geometry3D; 33 | if (typeof geod !== 'undefined') { 34 | 35 | var exteriorSurfaces = geod.abstractSolid.value.exterior.shell.surfaceMember; 36 | for (var surface of exteriorSurfaces) { 37 | var polygon = new Polygon(); 38 | polygon.fromJSON(surface.abstractSurface.value, parser); 39 | this.geometry.push(polygon); 40 | } 41 | 42 | if( geod.abstractSolid.value.interior != undefined){ 43 | var interiorSolids = geod.abstractSolid.value.interior; 44 | for( var interiorSolid of interiorSolids ){ 45 | for (var surface of interiorSolid.shell.surfaceMember) { 46 | var polygon = new Polygon(); 47 | polygon.fromJSON(surface.abstractSurface.value, parser); 48 | this.geometry.push(polygon); 49 | } 50 | } 51 | } 52 | } else { 53 | geod = cellGeometry.geometry2D; 54 | if (typeof geod !== 'undefined') { 55 | var surface = geod.abstractSurface; 56 | if (typeof surface !== 'undefined') { 57 | var polygon = new Polygon(); 58 | polygon.fromJSON(surface.value, parser); 59 | this.geometry.push(polygon); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/Indoor/IGMLParser.js: -------------------------------------------------------------------------------- 1 | import * as log from 'loglevel' 2 | 3 | import CellSpace from './Cellspace' 4 | import CellSpaceBoundary from './CellSpaceBoundary' 5 | 6 | import SpaceLayer from './SpaceLayer' 7 | import State from './State' 8 | import Transition from './Transition' 9 | 10 | export default class IGMLParser { 11 | constructor() { 12 | this.minmax = []; 13 | 14 | //to deal with xlinks 15 | this.placeholder = {}; 16 | this.gmlIdMap = {}; 17 | 18 | this.cells = []; 19 | this.cellBoundaries = []; 20 | this.multiLayeredGraph = []; //Graph array 21 | 22 | this.floorflag = 0 23 | } 24 | 25 | parse(igmlContent) { 26 | if (igmlContent == undefined) { 27 | log.debug("target IndoorGML is empty") 28 | return 29 | } 30 | 31 | var primalSpace = igmlContent.value.primalSpaceFeatures 32 | if (primalSpace !== undefined) { 33 | var cells = primalSpace.primalSpaceFeatures.cellSpaceMember 34 | if (cells !== undefined) { 35 | for (var cell of cells) { 36 | var c = new CellSpace() 37 | c.fromJSON(cell.cellSpace, this) 38 | this.cells.push(c) 39 | } 40 | } 41 | 42 | var cellboundarys = primalSpace.primalSpaceFeatures.cellSpaceBoundaryMember 43 | if (cellboundarys !== undefined) { 44 | for (var cellboundary of cellboundarys) { 45 | var cb = new CellSpaceBoundary() 46 | cb.fromJSON(cellboundary.cellSpaceBoundary, this) 47 | this.cellBoundaries.push(cb) 48 | } 49 | } 50 | } 51 | 52 | if (igmlContent.value.multiLayeredGraph !== undefined) { 53 | var mg = igmlContent.value.multiLayeredGraph.multiLayeredGraph; 54 | var layers = mg.spaceLayers; 55 | if (typeof layers !== 'undefined') { 56 | for (var layer of layers) { 57 | var layerMember = layer.spaceLayerMember 58 | for (var member of layerMember) { 59 | var spaceLayer = member.spaceLayer 60 | var spaceLayerId = spaceLayer.id 61 | 62 | var sl = new SpaceLayer(); 63 | sl.fromJSON(spaceLayer, this); 64 | this.multiLayeredGraph.push(sl); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | parseNodes(nodesArr, spaceLayer) { 72 | if (nodesArr !== undefined) { 73 | for (var nodes of nodesArr) { 74 | var stateMembers = nodes.stateMember; 75 | if (stateMembers !== undefined) { 76 | for (var stateMember of stateMembers) { 77 | var newState = new State(); 78 | newState.fromJSON(stateMember.state, this) 79 | spaceLayer.nodes.push(newState) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | parseEdges(edgeArr, spaceLayer) { 87 | if (edgeArr !== undefined) { 88 | for (var edges of edgeArr) { 89 | var transitionMembers = edges.transitionMember; 90 | if (transitionMembers !== undefined) { 91 | for (var transitionMember of transitionMembers) { 92 | var newTransition = new Transition(); 93 | newTransition.fromJSON(transitionMember.transition, this) 94 | spaceLayer.edges.push(newTransition) 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | parsePosOrPointPropertyOrPointRep(points, target) { 102 | if (points.value !== undefined) { 103 | var point = [points.value[0], points.value[2], -points.value[1]]; 104 | target.push(point[0], point[1], point[2]); 105 | this.setMinMax(point) 106 | } else { 107 | for (var i = 0; i < points.length; i++) { 108 | var point = [points[i].value.value[0], points[i].value.value[2], -points[i].value.value[1]]; 109 | target.push(point[0], point[1], point[2]); 110 | this.setMinMax(point) 111 | } 112 | } 113 | } 114 | 115 | setMinMax(point) { 116 | if (this.floorflag == 0) { 117 | this.floorflag = 1; 118 | this.minmax = [point[0], point[1], point[2], point[0], point[1], point[2]]; 119 | } else { 120 | this.minmax[0] = Math.max(this.minmax[0], point[0]); 121 | this.minmax[1] = Math.max(this.minmax[1], point[1]); 122 | this.minmax[2] = Math.max(this.minmax[2], point[2]); 123 | this.minmax[3] = Math.min(this.minmax[3], point[0]); 124 | this.minmax[4] = Math.min(this.minmax[4], point[1]); 125 | this.minmax[5] = Math.min(this.minmax[5], point[2]); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/components/Indoor/Indoor.js: -------------------------------------------------------------------------------- 1 | import IGMLParser from './IGMLParser' 2 | 3 | export default class Indoor { 4 | constructor() { 5 | this.minmax = []; 6 | 7 | //to deal with xlinks 8 | this.placeholder = {}; 9 | this.gmlIdMap = {}; 10 | 11 | this.cells = []; 12 | this.cellBoundaries = []; 13 | this.multiLayeredGraph = []; //Graph array 14 | } 15 | 16 | fromJSON(content) { 17 | var parser = new IGMLParser(); 18 | parser.parse(content); 19 | 20 | this.minmax = parser.minmax; 21 | this.cells = parser.cells; 22 | this.cellBoundaries = parser.cellBoundaries; 23 | this.multiLayeredGraph = parser.multiLayeredGraph; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Indoor/LineString.js: -------------------------------------------------------------------------------- 1 | import * as log from 'loglevel'; 2 | 3 | export default class LineString { 4 | constructor() { 5 | this.id; 6 | this.points = []; 7 | } 8 | 9 | fromJSON(content, parser) { 10 | this.id = content.id; 11 | var geometry = content.posOrPointPropertyOrPointRep; 12 | if (typeof geometry !== 'undefined') { 13 | parser.parsePosOrPointPropertyOrPointRep(geometry, this.points); 14 | for (var key in parser.placeHolder) { 15 | if (key == this.id) { 16 | var temp = new LineString(); 17 | temp.id = this.id + "_1"; 18 | for (var i = this.points.length - 3; i >= 0; i -= 3) { 19 | temp.points.push(this.points[i]); 20 | temp.points.push(this.points[i + 1]); 21 | temp.points.push(this.points[i + 2]); 22 | } 23 | parser.placeHolder[key].push(temp); 24 | break; 25 | } 26 | } 27 | parser.gmlIdMap[this.id] = this; 28 | } else { 29 | //assign xlinks 30 | var xlink = content.baseCurve.href; 31 | xlink = xlink.substr(1, xlink.length); 32 | for (var id in parser.gmlIdMap) { 33 | if (id === xlink) { 34 | var linestring = new LineString(); 35 | linestring.id = parser.gmlIdMap[xlink].id + "_1"; 36 | for (var i = parser.gmlIdMap[xlink].points.length - 3; i >= 0; i -= 3) { 37 | linestring.points.push(parser.gmlIdMap[xlink].points[i]); 38 | linestring.points.push(parser.gmlIdMap[xlink].points[i + 1]); 39 | linestring.points.push(parser.gmlIdMap[xlink].points[i + 2]); 40 | } 41 | this.geometry.push(linestring); 42 | break; 43 | } 44 | } 45 | if (this.geometry == 'undefined') { 46 | parser.placeHolder[xlink] = this.geometry; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Indoor/Point.js: -------------------------------------------------------------------------------- 1 | import * as log from 'loglevel'; 2 | 3 | export default class Point { 4 | constructor() { 5 | this.id 6 | this.coordinates = [] 7 | } 8 | 9 | fromJSON(content, parser) { 10 | this.id = content.id 11 | var pos = content.pos; 12 | if (typeof pos !== 'undefined') { 13 | parser.parsePosOrPointPropertyOrPointRep(pos, this.coordinates); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Indoor/Polygon.js: -------------------------------------------------------------------------------- 1 | import * as log from 'loglevel'; 2 | 3 | export default class Polygon { 4 | constructor() { 5 | this.id; 6 | this.exterior = []; //float array[1,2,3,4,5,6] 7 | this.interior = []; 8 | } 9 | 10 | fromJSON(content, parser) { 11 | this.id = content.id; 12 | 13 | var exterior = content.exterior 14 | if(exterior !== undefined) { 15 | var epoints = exterior.abstractRing.value.posOrPointPropertyOrPointRep; 16 | parser.parsePosOrPointPropertyOrPointRep(epoints, this.exterior); 17 | } else { 18 | console.log("weird") 19 | } 20 | 21 | var ipoints = content.interior; 22 | if (typeof ipoints !== 'undefined') { 23 | //TODO : multiple inner rings 24 | ipoints = ipoints[0].abstractRing.value.posOrPointPropertyOrPointRep; 25 | parser.parsePosOrPointPropertyOrPointRep(ipoints, this.interior); 26 | } 27 | 28 | //assign xlinks 29 | for (var key in parser.placeholder) { 30 | log.trace("key : ", key, "PlaceHolder : ", parser.placeholder); 31 | if (key == this.id) { 32 | var temp = new Polygon(); 33 | temp.id = this.id + "_1"; 34 | for (var i = this.exterior.length - 3; i >= 0; i -= 3) { 35 | temp.exterior.push(this.exterior[i]); 36 | temp.exterior.push(this.exterior[i + 1]); 37 | temp.exterior.push(this.exterior[i + 2]); 38 | } 39 | for (var i = this.interior.length - 3; i >= 0; i -= 3) { 40 | temp.interior.push(this.interior[i]); 41 | temp.interior.push(this.interior[i + 1]); 42 | temp.interior.push(this.interior[i + 2]); 43 | } 44 | parser.placeholder[key].push(temp); 45 | break; 46 | } 47 | } 48 | 49 | parser.gmlIdMap[this.id] = this; 50 | } 51 | 52 | reverse() { 53 | this.exterior.reverse() 54 | this.interior.reverse() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/Indoor/SpaceLayer.js: -------------------------------------------------------------------------------- 1 | import State from './State' 2 | import Transition from './Transition' 3 | 4 | export default class SpaceLayer { 5 | constructor() { 6 | this.id 7 | this.nodes = [] 8 | this.edges = [] 9 | } 10 | 11 | fromJSON(content, parser) { 12 | this.id = content.id; 13 | 14 | var nodesArr = content.nodes 15 | parser.parseNodes(nodesArr, this) 16 | 17 | var edgesArr = content.edges 18 | parser.parseEdges(edgesArr, this) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Indoor/State.js: -------------------------------------------------------------------------------- 1 | import Point from './Point' 2 | 3 | export default class State { 4 | constructor() { 5 | this.id 6 | this.name 7 | this.geometry //Point 8 | this.duality 9 | this.connects = [] 10 | } 11 | 12 | fromJSON(content, parser) { 13 | this.id = content.id; 14 | 15 | var n = content.name; 16 | if (typeof n !== 'undefined') { 17 | this.name = n[0].value; 18 | } 19 | 20 | var du = content.duality; 21 | if (typeof du !== 'undefined') { 22 | this.duality = du.href; 23 | } 24 | 25 | var connects = content.connects; 26 | if (typeof connects !== 'undefined') { 27 | for(var c of connects) { 28 | this.connects.push(c.href) 29 | } 30 | } 31 | 32 | var geometry = content.geometry 33 | if (typeof geometry !== 'undefined') { 34 | var p = new Point() 35 | p.fromJSON(geometry.point, parser) 36 | this.geometry = p 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/Indoor/Transition.js: -------------------------------------------------------------------------------- 1 | import LineString from './LineString' 2 | 3 | export default class Transition { 4 | constructor() { 5 | this.id 6 | this.name 7 | this.geometry //LineString 8 | this.duality 9 | this.connects = [] 10 | } 11 | 12 | fromJSON(content, parser) { 13 | this.id = content.id 14 | 15 | var n = content.name 16 | if (typeof n !== 'undefined') { 17 | this.name = n[0].value 18 | } 19 | 20 | var du = content.duality; 21 | if (typeof du !== 'undefined') { 22 | this.duality = du.href 23 | } 24 | 25 | var connects = content.connects; 26 | if (typeof connects !== 'undefined') { 27 | for(var c of connects) { 28 | this.connects.push(c.href) 29 | } 30 | } 31 | 32 | var geometry = content.geometry 33 | if (typeof geometry !== 'undefined') { 34 | var l = new LineString() 35 | l.fromJSON(geometry.abstractCurve.value, parser) 36 | this.geometry = l; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/Indoor/index.js: -------------------------------------------------------------------------------- 1 | import Indoor from './Indoor' 2 | 3 | export default Indoor 4 | -------------------------------------------------------------------------------- /src/components/Menubar/Menubar.js: -------------------------------------------------------------------------------- 1 | import { LinkContainer } from 'react-router-bootstrap' 2 | import { browserHistory } from 'react-router' 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | import ServiceManager from 'SvcManager' 6 | import './Menubar.scss' 7 | import { 8 | DropdownButton, 9 | NavDropdown, 10 | MenuItem, 11 | NavItem, 12 | Navbar, 13 | Button, 14 | Modal, 15 | Nav, 16 | FormGroup, 17 | FormControl 18 | } from 'react-bootstrap' 19 | import Uploader from 'Uploader' 20 | 21 | export default class Menubar extends React.Component { 22 | 23 | ///////////////////////////////////////////////////////// 24 | // 25 | // 26 | ///////////////////////////////////////////////////////// 27 | constructor (props, context) { 28 | super(props, context) 29 | } 30 | 31 | ///////////////////////////////////////////////////////// 32 | // 33 | // 34 | ///////////////////////////////////////////////////////// 35 | render() { 36 | return ( 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 151 | 152 | 160 | 161 | 162 | 163 | ) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/components/Menubar/Menubar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | padding-right: 10px; 3 | padding-left: 10px; 4 | margin-bottom: 0px; 5 | border-radius: 0px; 6 | } 7 | 8 | .navbar-brand, 9 | .navbar-nav li a { 10 | line-height: 60px; 11 | padding-top: 0; 12 | outline: none; 13 | height: 65px; 14 | } 15 | 16 | .navbar-nav li:hover{ 17 | background: #f4f4f4; 18 | cursor:pointer; 19 | } 20 | 21 | .navbar-nav li a:hover, 22 | .navbar-nav li a:hover label{ 23 | cursor:pointer; 24 | } 25 | 26 | .navbar .avatar { 27 | border: 2px solid #e97922; 28 | border-radius: 50%; 29 | position: relative; 30 | height: 40px; 31 | top: 2px; 32 | } 33 | 34 | .navbar-nav .nav-label { 35 | font-weight: normal; 36 | position: relative; 37 | top: 2px; 38 | } 39 | 40 | .navbar-brand>img { 41 | display: block; 42 | margin: 5px; 43 | float: left; 44 | } 45 | 46 | .navbar-brand > label { 47 | color: black; 48 | } 49 | 50 | .navbar .dropdown { 51 | overflow: hidden; 52 | } 53 | 54 | .navbar .dropdown.open{ 55 | overflow: visible; 56 | } 57 | 58 | .inviewer-navbar .dropdown-menu { 59 | padding: 0; 60 | } 61 | 62 | .inviewer-navbar .dropdown-menu > li { 63 | padding: 6px 0; 64 | margin: 0; 65 | } 66 | 67 | .inviewer-navbar .dropdown-menu > .divider { 68 | padding: 0; 69 | margin: 0; 70 | } 71 | 72 | .inviewer-navbar .dropdown-menu > li:hover { 73 | background: #e5e5e5; 74 | } 75 | 76 | .inviewer-navbar .dropdown-menu > li > a { 77 | height: 30px; 78 | line-height: 23px; 79 | padding: 3px 20px; 80 | } 81 | 82 | .inviewer-navbar .dropdown-menu > li > a:hover { 83 | background: #e5e5e5; 84 | } 85 | 86 | .inviewer-navbar .dropdown .caret { 87 | position: relative; 88 | top: -63px; 89 | left: 36px; 90 | } 91 | 92 | .inviewer-navbar .app-logo { 93 | position: relative; 94 | top: 2px; 95 | } 96 | 97 | .inviewer-navbar .menuItem { 98 | border-radius: 4px; 99 | } 100 | 101 | .inviewer-brand-item, { 102 | list-style: none; 103 | } 104 | 105 | .inviewer-brand-item > a, 106 | .inviewer-brand-item > a:hover { 107 | text-shadow: 0 0 0.5px #000; 108 | text-decoration: none; 109 | font-size: 24px; 110 | color: #daa228; 111 | } 112 | 113 | .inviewer-brand-item > a:hover { 114 | color: #a53a20; 115 | } 116 | 117 | .inviewer-navbar .dropdown-menu span { 118 | position: relative; 119 | color: #5d5f5f; 120 | top: 1px; 121 | } 122 | 123 | .inviewer-navbar a:hover span { 124 | color: #ff9000; 125 | } 126 | 127 | .navbar-default .navbar-nav > .active > a { 128 | background-color: #ffffff; 129 | } 130 | 131 | .search-bar { 132 | margin: 0; 133 | padding: 0; 134 | } 135 | 136 | .search-btn { 137 | color: #aaa; 138 | border-radius: 3px; 139 | font-weight: bold; 140 | transition: all 200ms ease-in-out; 141 | } 142 | 143 | .search-btn:hover { 144 | color: #fff; 145 | background-color: #8FBE00; 146 | } 147 | -------------------------------------------------------------------------------- /src/components/Menubar/index.js: -------------------------------------------------------------------------------- 1 | import Menubar from './Menubar' 2 | 3 | export default Menubar 4 | -------------------------------------------------------------------------------- /src/components/ModelUploader/ModelUploader.scss: -------------------------------------------------------------------------------- 1 | 2 | .model-uploader { 3 | overflow: hidden; 4 | height: 100%; 5 | 6 | .title { 7 | border-bottom: 1px solid #dedede; 8 | padding: 4px 0 4px 8px; 9 | background: #f9f9f9; 10 | text-align: left; 11 | font-weight: 800; 12 | font-size: 16px; 13 | cursor: default; 14 | color: #6d6d6d; 15 | } 16 | 17 | .title > span { 18 | position: relative; 19 | margin-right: 6px; 20 | font-size: 24px; 21 | color: #8e8e8e; 22 | top: 2px; 23 | } 24 | 25 | .title > label { 26 | margin: 0; 27 | } 28 | 29 | .login { 30 | height: calc(100% - 36px); 31 | padding-top: 10px; 32 | background: white; 33 | cursor: default; 34 | 35 | u { 36 | cursor: pointer; 37 | } 38 | } 39 | 40 | .limit { 41 | cursor: not-allowed; 42 | padding-top: 3px; 43 | color: #e44747; 44 | 45 | hr { 46 | margin: -6px 40px 3px 40px; 47 | } 48 | } 49 | 50 | .content { 51 | transition-property: all; 52 | transition-timing-function: ease; 53 | transition-duration: 0.5s; 54 | transition-delay: 0.0s; 55 | 56 | border: 2px solid #ffffff; 57 | height: calc(100% - 36px); 58 | padding: 8px 10px 0 0; 59 | background: white; 60 | cursor: pointer; 61 | } 62 | 63 | .content:hover { 64 | border-color: #1bb1f4; 65 | } 66 | 67 | hr { 68 | margin: 0 40px 10px 40px; 69 | } 70 | } 71 | 72 | .composite-dlg { 73 | 74 | .root-filename { 75 | border: 1px solid #d2d2d2; 76 | background: #fdfdfd; 77 | border-radius: 3px; 78 | padding-left: 6px; 79 | text-align: left; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/ModelUploader/index.js: -------------------------------------------------------------------------------- 1 | import ModelUploader from './ModelUploader' 2 | 3 | export default ModelUploader 4 | -------------------------------------------------------------------------------- /src/components/PaneManager/PaneManager.js: -------------------------------------------------------------------------------- 1 | import {ReflexContainer, ReflexSplitter} from 'react-reflex' 2 | import PaneElement from './PaneElement' 3 | import classNames from 'classnames' 4 | import PropTypes from 'prop-types' 5 | import './PaneManager.scss' 6 | import React from 'react' 7 | 8 | class PaneManager extends React.Component { 9 | 10 | ///////////////////////////////////////////////////////// 11 | // 12 | // 13 | ///////////////////////////////////////////////////////// 14 | static propTypes = { 15 | orientation: PropTypes.string.isRequired 16 | } 17 | 18 | ///////////////////////////////////////////////////////// 19 | // 20 | // 21 | ///////////////////////////////////////////////////////// 22 | constructor (props) { 23 | 24 | super (props) 25 | 26 | this.onLockSize = this.onLockSize.bind(this) 27 | 28 | this.state = { 29 | children: [] 30 | } 31 | } 32 | 33 | ///////////////////////////////////////////////////////// 34 | // 35 | // 36 | ///////////////////////////////////////////////////////// 37 | onLockSize (data) { 38 | 39 | this.state[data.paneId] = this.state[data.paneId] || { 40 | minSizeInit: data.minSize, 41 | maxSizeInit: data.maxSize 42 | } 43 | 44 | const locked = !this.state[data.paneId].sizeLocked 45 | 46 | this.state[data.paneId].sizeLocked = locked 47 | 48 | if (locked) { 49 | 50 | this.state[data.paneId].minSize = data.size 51 | this.state[data.paneId].maxSize = data.size 52 | 53 | } else { 54 | 55 | this.state[data.paneId].minSize = 56 | this.state[data.paneId].minSizeInit 57 | 58 | this.state[data.paneId].maxSize = 59 | this.state[data.paneId].maxSizeInit 60 | } 61 | 62 | this.cloneChildren (this.props.children, this.state) 63 | } 64 | 65 | ///////////////////////////////////////////////////////// 66 | // 67 | // 68 | ///////////////////////////////////////////////////////// 69 | getDirection (children, childIdx) { 70 | 71 | return ( 72 | childIdx === 0 73 | ? 1 74 | : (childIdx < children.length - 1 ? [1, -1] : -1) 75 | ) 76 | } 77 | 78 | ///////////////////////////////////////////////////////// 79 | // 80 | // 81 | ///////////////////////////////////////////////////////// 82 | flatMapChildren (children, fn) { 83 | 84 | return Array.prototype.concat.apply( 85 | [], React.Children.map(children, fn)) 86 | } 87 | 88 | ///////////////////////////////////////////////////////// 89 | // 90 | // 91 | ///////////////////////////////////////////////////////// 92 | guid (format = 'xxxxxxxxxxxx') { 93 | 94 | var d = new Date().getTime() 95 | 96 | return format.replace(/[xy]/g, (c) => { 97 | var r = (d + Math.random() * 16) % 16 | 0 98 | d = Math.floor(d / 16) 99 | return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16) 100 | }) 101 | } 102 | 103 | ///////////////////////////////////////////////////////////// 104 | // 105 | // 106 | ///////////////////////////////////////////////////////////// 107 | componentWillMount () { 108 | 109 | this.cloneChildren (this.props.children, this.state) 110 | } 111 | 112 | ///////////////////////////////////////////////////////////// 113 | // 114 | // 115 | ///////////////////////////////////////////////////////////// 116 | componentWillReceiveProps (props) { 117 | 118 | this.cloneChildren (props.children, this.state) 119 | } 120 | 121 | ///////////////////////////////////////////////////////////// 122 | // 123 | // 124 | ///////////////////////////////////////////////////////////// 125 | cloneChildren (children, state) { 126 | 127 | const nextChildren = this.flatMapChildren( 128 | children, (child, idx) => { 129 | 130 | const childId = 131 | (state.children.length > 2*idx) 132 | ? state.children[2*idx].props.id 133 | : null 134 | 135 | const childState = childId 136 | ? state[childId] 137 | : {} 138 | 139 | const newProps = Object.assign({}, child.props, { 140 | maxSize: child.props.maxSize || Number.MAX_VALUE, 141 | sizeLocked: child.props.sizeLocked || false, 142 | direction: this.getDirection(children, idx), 143 | minSize: child.props.minSize || 39, 144 | onLockSize: this.onLockSize, 145 | id: childId || this.guid(), 146 | title: child.props.title 147 | }, childState) 148 | 149 | const showSplitter = (idx < children.length - 1) 150 | 151 | const splitterStyle = { 152 | display: showSplitter ? 'block' : 'none' 153 | } 154 | 155 | const splitter = 156 | 159 | 160 | const paneElement = 161 | 162 | { React.cloneElement(child, child.props) } 163 | 164 | 165 | return [ paneElement, splitter] 166 | }) 167 | 168 | this.setState(Object.assign({}, 169 | this.state, state, { 170 | children: nextChildren 171 | })) 172 | } 173 | 174 | ///////////////////////////////////////////////////////// 175 | // 176 | // 177 | ///////////////////////////////////////////////////////// 178 | render () { 179 | 180 | return ( 181 | 182 | { this.state.children } 183 | 184 | ) 185 | } 186 | } 187 | 188 | export default PaneManager 189 | -------------------------------------------------------------------------------- /src/components/PaneManager/PaneManager.scss: -------------------------------------------------------------------------------- 1 | .pane-element { 2 | position: relative; 3 | height: 100%; 4 | width: 100%; 5 | } 6 | 7 | .pane-element > .title { 8 | border-bottom: 1px solid #cdcdcd; 9 | background-color: #F5F5F5; 10 | padding-right: 4px; 11 | position: relative; 12 | text-align: left; 13 | color: #373737; 14 | height: 40px; 15 | width: 100%; 16 | } 17 | 18 | .pane-element > .content { 19 | position: relative; 20 | overflow: hidden; 21 | height: 100%; 22 | width: 100%; 23 | } 24 | 25 | .pane-element > .title > label { 26 | white-space: nowrap; 27 | font-weight: normal; 28 | user-select: none; 29 | margin-left: 27px; 30 | margin-top: 10px; 31 | select: none; 32 | } 33 | 34 | .pane-element > .title > .controls { 35 | align-items: center; 36 | position: absolute; 37 | display: flex; 38 | height: 100%; 39 | right: 6px; 40 | } 41 | 42 | .pane-element > .title > .controls > button { 43 | border: 1px solid #c6c6c6; 44 | background-color: #e1e1e1; 45 | justify-content: center; 46 | align-items: center; 47 | border-radius: 6px; 48 | position: relative; 49 | margin-right: 4px; 50 | overflow: hidden; 51 | display: flex; 52 | outline: none; 53 | padding: 0px; 54 | height: 24px; 55 | width: 26px; 56 | } 57 | 58 | .pane-element > .title > .controls > button:hover { 59 | border: 1px solid #878787; 60 | background-color: #f5f5f5; 61 | } 62 | 63 | .pane-element > .title > .controls > button > label { 64 | position: relative; 65 | font-size: 20px; 66 | color: #f0fcff; 67 | top: -8px; 68 | } 69 | 70 | .pane-element > .title > .controls > button > span { 71 | margin: 1px auto 0 auto; 72 | pointer-events: none; 73 | position: relative; 74 | color: #9b9b9b; 75 | padding: 0px; 76 | height: 100%; 77 | width: 100%; 78 | left: -1px; 79 | } 80 | -------------------------------------------------------------------------------- /src/components/PaneManager/index.js: -------------------------------------------------------------------------------- 1 | import PaneElement from './PaneElement' 2 | import PaneManager from './PaneManager' 3 | 4 | export default PaneManager 5 | -------------------------------------------------------------------------------- /src/components/Panel/Panel.scss: -------------------------------------------------------------------------------- 1 | .react-panel { 2 | transition-timing-function: ease; 3 | transition-duration: 1.5s; 4 | transition-property: border, background; 5 | transition-delay: 0.0s; 6 | 7 | background: rgba(255, 255, 255, 0.7); 8 | box-shadow: 0 3px 5px rgba(0,0,0,.2); 9 | border: 1px solid #d9d9d9; 10 | border-radius: 4px; 11 | position: absolute; 12 | overflow: hidden; 13 | z-index: 1; 14 | } 15 | 16 | .react-panel:hover { 17 | background: rgba(255, 255, 255, 1.0); 18 | border: 1px solid #afafaf; 19 | z-index: 2; 20 | } 21 | 22 | .react-panel .title { 23 | margin: 0px 8px 0px 0px; 24 | background: #ededed; 25 | display: inline-flex; 26 | height: 35px; 27 | float: left; 28 | width: 100%; 29 | z-index: 1; 30 | 31 | &.draggable{ 32 | cursor: move; 33 | } 34 | } 35 | 36 | .react-panel .title > label { 37 | pointer-events: none; 38 | white-space: nowrap; 39 | font-weight: normal; 40 | user-select: none; 41 | margin-left: 27px; 42 | font-weight: bold; 43 | line-height: 14px; 44 | margin-top: 10px; 45 | text-align: left; 46 | font-size: 14px; 47 | select: none; 48 | float: left; 49 | } 50 | 51 | .react-panel > .content { 52 | height: calc(100% - 35px); 53 | margin-top: 35px; 54 | } 55 | 56 | .react-panel .content { 57 | background-color: transparent !important; 58 | } 59 | 60 | .react-panel > .resizer { 61 | transition-timing-function: ease; 62 | transition-duration: 1.5s; 63 | transition-property: background; 64 | transition-delay: 0.0s; 65 | 66 | transform: rotateZ(45deg); 67 | left: calc(100% - 15px); 68 | top: calc(100% - 15px); 69 | cursor: nwse-resize; 70 | position: absolute; 71 | background: #e1e1e1; 72 | height: 30px; 73 | width: 30px; 74 | z-index: 2; 75 | } 76 | 77 | .react-panel > .resizer:hover { 78 | background: #bbbbbb; 79 | } 80 | -------------------------------------------------------------------------------- /src/components/Panel/index.js: -------------------------------------------------------------------------------- 1 | import Panel from './Panel' 2 | 3 | export default Panel 4 | -------------------------------------------------------------------------------- /src/components/Settings/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEMLab/InViewer/f03df4cbdea47317ee2dc333b9869a05cfe6b6e1/src/components/Settings/index.js -------------------------------------------------------------------------------- /src/components/Stopwatch/Stopwatch.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////// 2 | // A stopwatch 3 | // 4 | /////////////////////////////////////////////////////////////////////////// 5 | export default class Stopwatch { 6 | 7 | constructor(){ 8 | 9 | this._lastTime = performance.now(); 10 | } 11 | 12 | start(){ 13 | 14 | this._lastTime = performance.now(); 15 | } 16 | 17 | getElapsedMs(){ 18 | 19 | var time = performance.now(); 20 | 21 | var elapsedMs = time - this._lastTime; 22 | 23 | this._lastTime = time; 24 | 25 | return elapsedMs; 26 | } 27 | } -------------------------------------------------------------------------------- /src/components/Stopwatch/index.js: -------------------------------------------------------------------------------- 1 | import Stopwatch from './Stopwatch' 2 | 3 | export default Stopwatch 4 | -------------------------------------------------------------------------------- /src/components/ThreeViewer/Config.js: -------------------------------------------------------------------------------- 1 | import TWEEN from 'tween.js'; 2 | 3 | // This object contains the state of the app 4 | export default { 5 | isDev: false, 6 | isShowingStats: true, 7 | isLoaded: false, 8 | isTweening: false, 9 | isRotating: true, 10 | isMouseMoving: false, 11 | isMouseOver: false, 12 | maxAnisotropy: 1, 13 | dpr: 1, 14 | easing: TWEEN.Easing.Quadratic.InOut, 15 | duration: 500, 16 | mesh: { 17 | enableHelper: false, 18 | wireframe: false, 19 | translucent: false, 20 | material: { 21 | color: 0xffffff, 22 | emissive: 0xffffff 23 | } 24 | }, 25 | fog: { 26 | color: 0xffffff, 27 | near: 0.0008 28 | }, 29 | camera: { 30 | fov: 40, 31 | near: 2, 32 | far: 1000, 33 | aspect: 1, 34 | posX: 0, 35 | posY: 30, 36 | posZ: 40 37 | }, 38 | controls: { 39 | autoRotate: true, 40 | autoRotateSpeed: -0.5, 41 | rotateSpeed: 0.5, 42 | zoomSpeed: 0.8, 43 | minDistance: 200, 44 | maxDistance: 600, 45 | minPolarAngle: Math.PI / 5, 46 | maxPolarAngle: Math.PI / 2, 47 | minAzimuthAngle: -Infinity, 48 | maxAzimuthAngle: Infinity, 49 | enableDamping: true, 50 | dampingFactor: 0.5, 51 | enableZoom: true, 52 | target: { 53 | x: 0, 54 | y: 0, 55 | z: 0 56 | } 57 | }, 58 | ambientLight: { 59 | enabled: false, 60 | color: 0x141414 61 | }, 62 | directionalLight: { 63 | enabled: true, 64 | color: 0xf0f0f0, 65 | intensity: 0.4, 66 | x: -75, 67 | y: 280, 68 | z: 150 69 | }, 70 | shadow: { 71 | enabled: true, 72 | helperEnabled: true, 73 | bias: 0, 74 | mapWidth: 2048, 75 | mapHeight: 2048, 76 | near: 250, 77 | far: 400, 78 | top: 100, 79 | right: 100, 80 | bottom: -100, 81 | left: -100 82 | }, 83 | pointLight: { 84 | enabled: true, 85 | color: 0xffffff, 86 | intensity: 0.34, 87 | distance: 115, 88 | x: 0, 89 | y: 0, 90 | z: 0 91 | }, 92 | hemiLight: { 93 | enabled: true, 94 | color: 0xc8c8c8, 95 | groundColor: 0xffffff, 96 | intensity: 0.55, 97 | x: 0, 98 | y: 0, 99 | z: 0 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /src/components/ThreeViewer/Light.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import Config from './Config'; 4 | 5 | // Sets up and places all lights in scene 6 | export default class Light { 7 | constructor(scene) { 8 | this.scene = scene; 9 | 10 | this.init(); 11 | } 12 | 13 | init() { 14 | // Ambient 15 | this.ambientLight = new THREE.AmbientLight(Config.ambientLight.color); 16 | this.ambientLight.visible = Config.ambientLight.enabled; 17 | 18 | // Point light 19 | this.pointLight = new THREE.PointLight(Config.pointLight.color, Config.pointLight.intensity, Config.pointLight.distance); 20 | this.pointLight.position.set(Config.pointLight.x, Config.pointLight.y, Config.pointLight.z); 21 | this.pointLight.visible = Config.pointLight.enabled; 22 | 23 | // Directional light 24 | this.directionalLight = new THREE.DirectionalLight(Config.directionalLight.color, Config.directionalLight.intensity); 25 | this.directionalLight.position.set(Config.directionalLight.x, Config.directionalLight.y, Config.directionalLight.z); 26 | this.directionalLight.visible = Config.directionalLight.enabled; 27 | 28 | // Shadow map 29 | this.directionalLight.castShadow = Config.shadow.enabled; 30 | this.directionalLight.shadow.bias = Config.shadow.bias; 31 | this.directionalLight.shadow.camera.near = Config.shadow.near; 32 | this.directionalLight.shadow.camera.far = Config.shadow.far; 33 | this.directionalLight.shadow.camera.left = Config.shadow.left; 34 | this.directionalLight.shadow.camera.right = Config.shadow.right; 35 | this.directionalLight.shadow.camera.top = Config.shadow.top; 36 | this.directionalLight.shadow.camera.bottom = Config.shadow.bottom; 37 | this.directionalLight.shadow.mapSize.width = Config.shadow.mapWidth; 38 | this.directionalLight.shadow.mapSize.height = Config.shadow.mapHeight; 39 | 40 | // Shadow camera helper 41 | this.directionalLightHelper = new THREE.CameraHelper(this.directionalLight.shadow.camera); 42 | this.directionalLightHelper.visible = Config.shadow.helperEnabled; 43 | 44 | // Hemisphere light 45 | this.hemiLight = new THREE.HemisphereLight(Config.hemiLight.color, Config.hemiLight.groundColor, Config.hemiLight.intensity); 46 | this.hemiLight.position.set(Config.hemiLight.x, Config.hemiLight.y, Config.hemiLight.z); 47 | this.hemiLight.visible = Config.hemiLight.enabled; 48 | } 49 | 50 | place(lightName) { 51 | switch(lightName) { 52 | case 'ambient': 53 | this.scene.add(this.ambientLight); 54 | break; 55 | 56 | case 'directional': 57 | this.scene.add(this.directionalLight); 58 | this.scene.add(this.directionalLightHelper); 59 | break; 60 | 61 | case 'point': 62 | this.scene.add(this.pointLight); 63 | break; 64 | 65 | case 'hemi': 66 | this.scene.add(this.hemiLight); 67 | break; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/ThreeViewer/Renderer.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | //import Config from '../../data/config'; 4 | 5 | // Main webGL renderer class 6 | export default class Renderer { 7 | constructor(scene, container) { 8 | // Properties 9 | this.scene = scene; 10 | this.container = container; 11 | 12 | // Create WebGL renderer and set its antialias 13 | this.threeRenderer = new THREE.WebGLRenderer({antialias: true}); 14 | 15 | // Set clear color to fog to enable fog or to hex color for no fog 16 | this.threeRenderer.setClearColor(scene.fog.color); 17 | this.threeRenderer.setPixelRatio(window.devicePixelRatio); // For retina 18 | 19 | // Appends canvas 20 | this.container.appendChild(this.threeRenderer.domElement); 21 | 22 | // Shadow map options 23 | this.threeRenderer.shadowMap.enabled = true; 24 | this.threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; 25 | 26 | // Get anisotropy for textures 27 | //Config.maxAnisotropy = this.threeRenderer.getMaxAnisotropy(); 28 | 29 | // Initial size update set to canvas container 30 | //this.updateSize(); 31 | 32 | // Listeners 33 | document.addEventListener('DOMContentLoaded', () => this.updateSize(), false); 34 | window.addEventListener('resize', () => this.updateSize(), false); 35 | } 36 | 37 | updateSize() { 38 | this.threeRenderer.setSize(this.container.offsetWidth, this.container.offsetHeight); 39 | } 40 | 41 | _render(scene, camera) { 42 | // Renders scene to canvas target 43 | this.threeRenderer.render(scene, camera); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/ThreeViewer/ThreeViewer.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import TWEEN from 'tween.js' 3 | import Viewport from './Viewport' 4 | import Light from './Light' 5 | 6 | import CameraControls from 'camera-controls'; 7 | CameraControls.install( { THREE: THREE } ); 8 | 9 | export default class ThreeViewer { 10 | 11 | constructor(container) { 12 | this.container = container 13 | 14 | this.width = this.container.offsetWidth 15 | this.height = this.container.offsetHeight 16 | 17 | // Main scene creation 18 | this.scene = new THREE.Scene() 19 | this.scene.name = 'Main Scene' 20 | 21 | // scene helper for 22 | this.sceneHelper = new THREE.Scene() 23 | this.sceneHelper.name = 'Scene Helpers' 24 | 25 | var ambientLight = new THREE.AmbientLight( 0x404040 ); // soft white light 26 | this.scene.add( ambientLight ); 27 | 28 | var light1 = new THREE.DirectionalLight( 0xffffff, 0.5 ); 29 | light1.castShadow = true; 30 | light1.shadowCameraVisible = true; 31 | light1.position.set( 10, 10, 5 ).normalize(); 32 | this.scene.add( light1 ); 33 | 34 | var pointLight = new THREE.PointLight(0xFFFFFF, 1, 100000); 35 | this.scene.add( pointLight ); 36 | 37 | this.DEFAULT_CAMERA = new THREE.PerspectiveCamera(50, this.width / this.height, 1, 4000) 38 | this.DEFAULT_CAMERA.name = 'Camera' 39 | //this.DEFAULT_CAMERA.lookAt(this.scene.position) 40 | this.DEFAULT_CAMERA.position.z = 20 41 | //this.DEFAULT_CAMERA.lookAt(new THREE.Vector3()) 42 | 43 | // Start Three clock 44 | this.clock = new THREE.Clock() 45 | 46 | this.camera = this.DEFAULT_CAMERA.clone() 47 | 48 | this.objects = []; 49 | 50 | // Main renderer constructor 51 | this.renderer = new Viewport(this.scene, this.sceneHelper, this.camera, this.container) 52 | 53 | //this.controls = new Controls(this.camera, this.container); 54 | 55 | this.controls = new CameraControls( this.camera, this.container ) 56 | 57 | /* 58 | this.add(new Cube({ 59 | width: 10, 60 | height: 10, 61 | depth: 10 62 | })); 63 | */ 64 | 65 | this.render() 66 | } 67 | 68 | setObject(object) { 69 | this.objects.push(object); 70 | this.scene.add(object); 71 | this.render() 72 | } 73 | 74 | resize() { 75 | this.renderer.resize() 76 | } 77 | 78 | clear() { 79 | for(var o of this.objects) { 80 | this.scene.remove(o); 81 | } 82 | this.render() 83 | } 84 | 85 | render() { 86 | 87 | // Delta time is sometimes needed for certain updates 88 | const delta = this.clock.getDelta() 89 | const needsUpdate = this.controls.update( delta ) 90 | //if ( needsUpdate ) { 91 | //TWEEN.update() 92 | this.renderer.render() 93 | //} 94 | 95 | requestAnimationFrame(() => { 96 | this.render() 97 | }) 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/components/ThreeViewer/Viewport.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | //import Config from '../../data/config'; 4 | 5 | // Main webGL renderer class 6 | export default class Viewport { 7 | constructor(scene, sceneHelper, camera, container) { 8 | // Properties 9 | this.scene = scene; 10 | this.sceneHelper = sceneHelper; 11 | this.camera = camera; 12 | this.container = container; 13 | 14 | // grid helper 15 | var grid = new THREE.GridHelper(50, 10, 0xbbbbbb, 0x888888); 16 | grid.visible = false; 17 | this.sceneHelper.add(grid); 18 | 19 | var axesHelper = new THREE.AxesHelper( 5 ); 20 | axesHelper.visible = false; 21 | this.sceneHelper.add( axesHelper ); 22 | 23 | var skyboxGeometry = new THREE.CubeGeometry(200, 200, 200); 24 | var skyboxMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.BackSide }); 25 | var skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial); 26 | this.scene.add(skybox); 27 | 28 | // Create WebGL renderer and set its antialias 29 | this.threeRenderer = new THREE.WebGLRenderer( { antialias: false } ); 30 | this.threeRenderer.autoClear = false; 31 | this.threeRenderer.autoUpdateScene = false; 32 | this.threeRenderer.shadowMapEnabled = true; 33 | this.threeRenderer.shadowMapSoft = true; 34 | 35 | // Appends canvas 36 | var clearColor = 0xffffff; 37 | this.threeRenderer.setClearColor(clearColor); 38 | this.threeRenderer.setPixelRatio(window.devicePixelRatio); // For retina 39 | this.threeRenderer.setSize(this.container.offsetWidth, this.container.offsetHeight); 40 | this.container.appendChild(this.threeRenderer.domElement); 41 | 42 | // Shadow map options 43 | // this.threeRenderer.shadowMap.enabled = true; 44 | // this.threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; 45 | 46 | // Get anisotropy for textures 47 | //Config.maxAnisotropy = this.threeRenderer.getMaxAnisotropy(); 48 | 49 | this.raycaster = new THREE.Raycaster; 50 | 51 | // Initial size update set to canvas container 52 | //this.resize(); 53 | //this.render(); 54 | 55 | // Listeners 56 | //this.container.addEventListener('DOMContentLoaded', () => this.updateSize(), false); 57 | //this.container.addEventListener('resize', () => this.updateSize(), false); 58 | } 59 | 60 | resize() { 61 | var width = this.container.offsetWidth 62 | var height = this.container.offsetHeight 63 | 64 | this.camera.aspect = width / height; 65 | this.camera.updateProjectionMatrix(); 66 | 67 | this.threeRenderer.setSize(width, height); 68 | } 69 | 70 | render() { 71 | // Renders scene to canvas target 72 | this.scene.updateMatrixWorld(); 73 | this.sceneHelper.updateMatrixWorld(); 74 | 75 | this.threeRenderer.clear(); 76 | this.threeRenderer.render(this.scene, this.camera); 77 | this.threeRenderer.render(this.sceneHelper, this.camera); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/ThreeViewer/index.js: -------------------------------------------------------------------------------- 1 | import ThreeViewer from './ThreeViewer' 2 | 3 | export default ThreeViewer 4 | -------------------------------------------------------------------------------- /src/components/ThreeViewerMock/ThreeViewerMock.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | class Cube { 4 | constructor(size) { 5 | this.geometry = new THREE.BoxGeometry(size.width, size.height, size.depth); 6 | this.material = new THREE.MeshBasicMaterial({ 7 | color: 0x00ff00 8 | }); 9 | 10 | this.mesh = new THREE.Mesh(this.geometry, this.material); 11 | } 12 | 13 | update() { 14 | this.mesh.rotation.x += 0.1; 15 | this.mesh.rotation.y += 0.1; 16 | } 17 | 18 | getMesh() { 19 | return this.mesh; 20 | } 21 | } 22 | 23 | export default class ThreeViewerMock { 24 | constructor(container) { 25 | this.objects = []; 26 | this.container = container; 27 | this.createScene(); 28 | } 29 | 30 | createScene() { 31 | this.scene = new THREE.Scene(); 32 | this.camera = new THREE.PerspectiveCamera(60, this.container.offsetWidth / this.container.offsetHeight, 1, 2000); 33 | this.camera.position.z = 20; 34 | 35 | this.renderer = new THREE.WebGLRenderer(); 36 | this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight); 37 | this.container.appendChild(this.renderer.domElement); 38 | 39 | this.render(); 40 | 41 | this.add(new Cube({ 42 | width: 10, 43 | height: 10, 44 | depth: 10 45 | })); 46 | } 47 | 48 | render() { 49 | requestAnimationFrame(() => { 50 | this.render(); 51 | }); 52 | 53 | this.objects.forEach((object) => { 54 | object.update(); 55 | }); 56 | 57 | this.renderer.render(this.scene, this.camera); 58 | } 59 | 60 | add(mesh) { 61 | this.objects.push(mesh); 62 | this.scene.add(mesh.getMesh()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/ThreeViewerMock/index.js: -------------------------------------------------------------------------------- 1 | import ThreeViewerMock from './ThreeViewerMock' 2 | 3 | export default ThreeViewerMock 4 | -------------------------------------------------------------------------------- /src/components/Uploader/Uploader.scss: -------------------------------------------------------------------------------- 1 | 2 | .igml-uploader { 3 | overflow: hidden; 4 | height: 100%; 5 | 6 | .title { 7 | border-bottom: 1px solid #dedede; 8 | padding: 4px 0 4px 8px; 9 | background: #f9f9f9; 10 | text-align: left; 11 | font-weight: 800; 12 | font-size: 16px; 13 | cursor: default; 14 | color: #6d6d6d; 15 | } 16 | 17 | .title > span { 18 | position: relative; 19 | margin-right: 6px; 20 | font-size: 24px; 21 | color: #8e8e8e; 22 | top: 2px; 23 | } 24 | 25 | .title > label { 26 | margin: 0; 27 | } 28 | 29 | .login { 30 | height: calc(100% - 36px); 31 | padding-top: 10px; 32 | background: white; 33 | cursor: default; 34 | 35 | u { 36 | cursor: pointer; 37 | } 38 | } 39 | 40 | .limit { 41 | cursor: not-allowed; 42 | padding-top: 3px; 43 | color: #e44747; 44 | 45 | hr { 46 | margin: -6px 40px 3px 40px; 47 | } 48 | } 49 | 50 | .content { 51 | transition-property: all; 52 | transition-timing-function: ease; 53 | transition-duration: 0.5s; 54 | transition-delay: 0.0s; 55 | 56 | border: 2px solid #ffffff; 57 | height: calc(100% - 36px); 58 | padding: 8px 10px 0 0; 59 | background: white; 60 | cursor: pointer; 61 | } 62 | 63 | .content:hover { 64 | border-color: #1bb1f4; 65 | } 66 | 67 | hr { 68 | margin: 0 40px 10px 40px; 69 | } 70 | } 71 | 72 | .composite-dlg { 73 | 74 | .root-filename { 75 | border: 1px solid #d2d2d2; 76 | background: #fdfdfd; 77 | border-radius: 3px; 78 | padding-left: 6px; 79 | text-align: left; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/Uploader/index.js: -------------------------------------------------------------------------------- 1 | import Uploader from './Uploader' 2 | 3 | export default Uploader 4 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.App/ViewerApp.scss: -------------------------------------------------------------------------------- 1 | .viewer-app-container { 2 | position: relative; 3 | overflow: hidden; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | .viewer-container { 9 | position: absolute; 10 | overflow: hidden; 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | .viewer-panels-container { 16 | position: absolute; 17 | overflow: visible; 18 | height: 0; 19 | width: 0; 20 | left: 0; 21 | top: 0; 22 | } 23 | 24 | .adsk-viewing-viewer .docking-panel { 25 | text-align: left; 26 | 27 | .docking-panel-close { 28 | top: -6px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.App/index.js: -------------------------------------------------------------------------------- 1 | import ViewerApp from './ViewerApp' 2 | 3 | export default ViewerApp 4 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.Container/Viewer.Container.scss: -------------------------------------------------------------------------------- 1 | .viewer-app-container { 2 | position: relative; 3 | overflow: hidden; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | .viewer-container { 9 | position: absolute; 10 | overflow: hidden; 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | .inviewer .data-pane { 16 | background-color: #fdfdfd; 17 | height: 100%; 18 | } 19 | 20 | .viewer-panels-container { 21 | position: absolute; 22 | overflow: visible; 23 | height: 0; 24 | width: 0; 25 | left: 0; 26 | top: 0; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.Container/index.js: -------------------------------------------------------------------------------- 1 | import ViewerContainer from './Viewer.Container' 2 | 3 | export default ViewerContainer 4 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.ExtensionBase/Viewer.ExtensionBase.js: -------------------------------------------------------------------------------- 1 | import EventsEmitter from 'EventsEmitter' 2 | 3 | export default class ViewerExtensionBase extends EventsEmitter { 4 | ///////////////////////////////////////////////////////// 5 | // Class constructor 6 | // 7 | ///////////////////////////////////////////////////////// 8 | constructor (viewer, options = {}, defaultOptions = {}) { 9 | 10 | super (viewer) 11 | 12 | // bindings 13 | 14 | this.defaultOptions = defaultOptions 15 | 16 | this.options = Object.assign({}, 17 | defaultOptions, 18 | options) 19 | 20 | this.viewer = viewer 21 | 22 | this.initializeEvents () 23 | } 24 | 25 | initializeEvents () { 26 | if (this.options.events) { 27 | this.events = this.options.events 28 | 29 | // register options 30 | 31 | this.viewerEvents = [] 32 | 33 | this.viewerEvents.forEach((event) => { 34 | 35 | this.viewerEvent(event.id, this[event.handler]) 36 | }) 37 | } 38 | 39 | viewerEvent (eventId, handler) { 40 | if (handler) { 41 | this.viewer.addEventListener (eventId, handler) 42 | return 43 | } 44 | 45 | const eventIds = Array.isArray(eventId) 46 | ? eventId : [eventId] 47 | 48 | const eventTasks = eventIds.map((id) => { 49 | return new Promise ((resolve) => { 50 | const __handler = (args) => { 51 | this.viewer.removeEventListener (id, __handler) 52 | resolve (args) 53 | } 54 | this.viewer.addEventListener (id, __handler) 55 | }) 56 | }) 57 | 58 | return Promise.all (eventsTaks) 59 | } 60 | } 61 | 62 | ///////////////////////////////////////////////////////// 63 | // Extension Id 64 | // 65 | ///////////////////////////////////////////////////////// 66 | static get ExtensionId() { 67 | return 'Viewer.ExtensionBase' 68 | } 69 | 70 | ///////////////////////////////////////////////////////// 71 | // Load callback 72 | // 73 | ///////////////////////////////////////////////////////// 74 | load () { 75 | return true 76 | } 77 | 78 | ///////////////////////////////////////////////////////// 79 | // Unload callback 80 | // 81 | ///////////////////////////////////////////////////////// 82 | unload () { 83 | 84 | this.viewerEvents.forEach((event) => { 85 | 86 | this.viewer.removeEventListener( 87 | event.id, this[event.handler]) 88 | }) 89 | 90 | if (this.events) { 91 | 92 | // unregister events 93 | } 94 | 95 | this.off() 96 | 97 | return true 98 | } 99 | 100 | ///////////////////////////////////////////////////////// 101 | // Reload callback, in case the extension is re-loaded 102 | // more than once 103 | // 104 | ///////////////////////////////////////////////////////// 105 | reload (options = {}) { 106 | 107 | this.options = Object.assign({}, 108 | this.defaultOptions, 109 | this.options, 110 | options) 111 | 112 | return true 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.ExtensionBase/index.js: -------------------------------------------------------------------------------- 1 | import ViewerExtensionBase from './ViewerExtensionBase' 2 | 3 | export default ViewerExtensionBase 4 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.ExtensionManager/Viewer.ExtensionManager.scss: -------------------------------------------------------------------------------- 1 | .extension-manager .title { 2 | float: left; 3 | } 4 | 5 | .extension-manager .content { 6 | background: white; 7 | } 8 | 9 | .extension-manager .title > label{ 10 | white-space: nowrap; 11 | font-weight: normal; 12 | user-select: none; 13 | margin-left: 27px; 14 | margin-top: 10px; 15 | select: none; 16 | width: 80px; 17 | } 18 | 19 | .extension-manager .extension-list { 20 | height: calc(100% - 12px); 21 | background-color: white; 22 | margin: 6px 0 0 0; 23 | overflow: scroll; 24 | } 25 | 26 | .extension-manager .item { 27 | transition-timing-function: ease; 28 | transition-duration: 1.5s; 29 | transition-property: all; 30 | transition-delay: 0.0s; 31 | background: #ededed; 32 | 33 | border: 1px solid #aeaeae; 34 | width: calc(100% - 10px); 35 | white-space: nowrap; 36 | border-radius: 4px; 37 | overflow: hidden; 38 | cursor: pointer; 39 | height: 24px; 40 | padding: 2px; 41 | margin: 5px; 42 | } 43 | 44 | .extension-manager .item:hover { 45 | background: rgba(0, 105, 245, 0.35); 46 | border: 1px solid #1bb1f4; 47 | } 48 | 49 | .extension-manager .item.loading { 50 | cursor: progress; 51 | } 52 | 53 | .extension-manager .item.enabled { 54 | background: rgba(2, 160, 29, 0.72); 55 | border: 2px solid #00f41b; 56 | } 57 | 58 | .extension-manager .item > label { 59 | pointer-events: none; 60 | font-weight: normal; 61 | margin: 0 0 0 10px; 62 | position: relative; 63 | float: left; 64 | top: -2px; 65 | } 66 | 67 | .extension-pane { 68 | height: 100%; 69 | width: 100%; 70 | } 71 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.ExtensionManager/index.js: -------------------------------------------------------------------------------- 1 | import ViewerExtensionManager from './Viewing.ExtensionManager' 2 | 3 | export default ViewerExtensionManager 4 | -------------------------------------------------------------------------------- /src/components/Viewer/components/Viewer.Extensions/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEMLab/InViewer/f03df4cbdea47317ee2dc333b9869a05cfe6b6e1/src/components/Viewer/components/Viewer.Extensions/README.md -------------------------------------------------------------------------------- /src/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import { browserHistory, Router } from 'react-router' 2 | import { Provider } from 'react-redux' 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | 6 | class AppContainer extends React.Component { 7 | 8 | static propTypes = { 9 | routes : PropTypes.object.isRequired, 10 | store : PropTypes.object.isRequired 11 | } 12 | 13 | ///////////////////////////////////////////////////////// 14 | // 15 | // 16 | ///////////////////////////////////////////////////////// 17 | constructor (props) { 18 | 19 | super (props) 20 | } 21 | 22 | ///////////////////////////////////////////////////////// 23 | // 24 | // 25 | ///////////////////////////////////////////////////////// 26 | componentDidMount () { 27 | 28 | } 29 | 30 | ///////////////////////////////////////////////////////// 31 | // 32 | // 33 | ///////////////////////////////////////////////////////// 34 | shouldComponentUpdate () { 35 | 36 | return false 37 | } 38 | 39 | ///////////////////////////////////////////////////////// 40 | // 41 | // 42 | ///////////////////////////////////////////////////////// 43 | render () { 44 | 45 | const { routes, store } = this.props 46 | 47 | return ( 48 | 49 |
50 | 54 |
55 |
56 | ) 57 | } 58 | } 59 | 60 | export default AppContainer 61 | -------------------------------------------------------------------------------- /src/electron.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron') 2 | 3 | let win = null; 4 | 5 | function createWindow() { 6 | // Initialize the window to our specified dimensions 7 | win = new BrowserWindow({width: 1000, height: 600}); 8 | win.setMenu(null); 9 | // Specify entry point 10 | win.loadURL('http://localhost:3000'); 11 | 12 | // Show dev tools 13 | // Remove this line before distributing 14 | // win.webContents.openDevTools() 15 | 16 | // Remove window once app is closed 17 | win.on('closed', function () { 18 | win = null; 19 | }); 20 | } 21 | 22 | 23 | app.on('ready', function () { 24 | 25 | createWindow(); 26 | 27 | }); 28 | 29 | app.on('activate', () => { 30 | if (win === null) { 31 | createWindow() 32 | } 33 | }) 34 | 35 | app.on('window-all-closed', function () { 36 | if (process.platform != 'darwin') { 37 | app.quit(); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InViewer - IndoorGML Viewer 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import 'bootstrap-webpack' 4 | import AppContainer from './containers/AppContainer' 5 | import store from './store' 6 | import { Provider } from 'react-redux' 7 | 8 | // Render Setup 9 | // ------------------------------------ 10 | const MOUNT_NODE = document.getElementById('root') 11 | 12 | let render = () => { 13 | const routes = require('./routes/index').default(store) 14 | 15 | ReactDOM.render( 16 | 17 | 21 | , 22 | MOUNT_NODE 23 | ) 24 | } 25 | 26 | // Development Tools 27 | // ------------------------------------ 28 | if (__DEV__) { 29 | if (module.hot) { 30 | const renderApp = render 31 | const renderError = (error) => { 32 | const RedBox = require('redbox-react').default 33 | 34 | ReactDOM.render(, MOUNT_NODE) 35 | } 36 | 37 | render = () => { 38 | try { 39 | renderApp() 40 | } catch (e) { 41 | console.error(e) 42 | renderError(e) 43 | } 44 | } 45 | 46 | // Setup hot module replacement 47 | module.hot.accept([ 48 | './routes/index', 49 | ], () => 50 | setImmediate(() => { 51 | ReactDOM.unmountComponentAtNode(MOUNT_NODE) 52 | render() 53 | }) 54 | ) 55 | } 56 | } 57 | 58 | // Let's Go! 59 | // ------------------------------------ 60 | if (!__TEST__) render() 61 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import autobind from 'autobind-decorator' 2 | import NotificationsSystem from 'reapop' 3 | import ServiceManager from 'SvcManager' 4 | 5 | import '!style-loader!css-loader!bootstrap/dist/css/bootstrap.min.css'; 6 | import '!style-loader!css-loader!font-awesome/css/font-awesome.min.css'; 7 | import theme from 'reapop-theme-bootstrap' 8 | import 'core.scss' 9 | 10 | import PropTypes from 'prop-types' 11 | import 'react-reflex/styles.css' 12 | import Header from 'Header' 13 | import React from 'react' 14 | 15 | export default class CoreLayout extends React.Component { 16 | 17 | ///////////////////////////////////////////////////////// 18 | // 19 | // 20 | ///////////////////////////////////////////////////////// 21 | static propTypes = { 22 | children : PropTypes.element.isRequired 23 | } 24 | 25 | ///////////////////////////////////////////////////////// 26 | // 27 | // 28 | ///////////////////////////////////////////////////////// 29 | constructor (props) { 30 | 31 | super(props) 32 | } 33 | 34 | ///////////////////////////////////////////////////////// 35 | // 36 | // 37 | ///////////////////////////////////////////////////////// 38 | componentWillMount () { 39 | this.notifySvc = 40 | ServiceManager.getService( 41 | 'NotifySvc') 42 | 43 | this.notifySvc.initialize ({ 44 | remove: this.props.removeNotifications, 45 | update: this.props.updateNotification, 46 | add: this.props.addNotification 47 | }) 48 | 49 | this.dialogSvc = 50 | ServiceManager.getService( 51 | 'DialogSvc') 52 | 53 | this.dialogSvc.setComponent(this) 54 | } 55 | 56 | ///////////////////////////////////////////////////////// 57 | // 58 | // 59 | ///////////////////////////////////////////////////////// 60 | componentWillUnmount () { 61 | 62 | } 63 | 64 | ///////////////////////////////////////////////////////// 65 | // 66 | // 67 | ///////////////////////////////////////////////////////// 68 | render () { 69 | 70 | return ( 71 |
72 |
73 | 74 |
75 |
76 |
77 | {this.props.children} 78 |
79 | { this.dialogSvc.render() } 80 |
81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/CoreLayoutContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import CoreLayout from './CoreLayout' 4 | 5 | import { 6 | layoutChange, 7 | themeChange 8 | } from '../../store/app' 9 | 10 | import { 11 | removeNotifications, 12 | updateNotification, 13 | addNotification 14 | } from 'reapop' 15 | 16 | const mapDispatchToProps = { 17 | removeNotifications, 18 | updateNotification, 19 | addNotification 20 | } 21 | 22 | const mapStateToProps = (state) => ({ 23 | appState: state.app 24 | }) 25 | 26 | export default connect( 27 | mapStateToProps, 28 | mapDispatchToProps)(CoreLayout) 29 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/index.js: -------------------------------------------------------------------------------- 1 | import CoreLayoutContainer from './CoreLayoutContainer' 2 | 3 | export default CoreLayoutContainer 4 | -------------------------------------------------------------------------------- /src/layouts/PageLayout/PageLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IndexLink, Link } from 'react-router' 3 | import PropTypes from 'prop-types' 4 | import './PageLayout.scss' 5 | 6 | export const PageLayout = ({ children }) => ( 7 |
8 |

React Redux Starter Kit

9 | Home 10 | {' · '} 11 | Counter 12 |
13 | {children} 14 |
15 |
16 | ) 17 | PageLayout.propTypes = { 18 | children: PropTypes.node, 19 | } 20 | 21 | export default PageLayout 22 | -------------------------------------------------------------------------------- /src/layouts/PageLayout/PageLayout.scss: -------------------------------------------------------------------------------- 1 | .page-layout__viewport { 2 | padding-top: 4rem; 3 | } 4 | 5 | .page-layout__nav-item--active { 6 | font-weight: bold; 7 | text-decoration: underline; 8 | } 9 | -------------------------------------------------------------------------------- /src/normalize.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | 3 | ** Browser Normalizer ** 4 | 5 | This file is responsible for normalizing the browser environment before 6 | the application starts. Doing this allows us to safely use modern language 7 | features even when the end user is running an older browser. 8 | 9 | The following polyfills are included by default: 10 | 11 | 1) Object.assign 12 | 2) Promise 13 | 3) Fetch 14 | 15 | ====================================================== */ 16 | 17 | // 1) Object.assign 18 | // ------------------------------------ 19 | // We can't rely on Object.assign being a function since it may be buggy, so 20 | // defer to `object-assign`. If our Object.assign implementation is correct 21 | // (determined by `object-assign` internally) the polyfill will be discarded 22 | // and the native implementation used. 23 | Object.assign = require('object-assign') 24 | 25 | // 2) Promise 26 | // ------------------------------------ 27 | if (typeof Promise === 'undefined') { 28 | require('promise/lib/rejection-tracking').enable() 29 | window.Promise = require('promise/lib/es6-extensions.js') 30 | } 31 | 32 | // 3) Fetch 33 | // ------------------------------------ 34 | // Fetch polyfill depends on a Promise implementation, so it must come after 35 | // the feature check / polyfill above. 36 | if (typeof window.fetch === 'undefined') { 37 | require('whatwg-fetch') 38 | } 39 | -------------------------------------------------------------------------------- /src/routes/Configurator/ConfiguratorView/ConfiguratorHomeView/ConfiguratorHomeView.js: -------------------------------------------------------------------------------- 1 | import ContentEditable from 'react-contenteditable' 2 | import { browserHistory } from 'react-router' 3 | import BaseComponent from 'BaseComponent' 4 | import ServiceManager from 'SvcManager' 5 | import Item from './ConfiguratorItem' 6 | import './ConfiguratorHomeView.scss' 7 | import { Link } from 'react-router' 8 | import React from 'react' 9 | import Label from 'Label' 10 | 11 | class ConfiguratorHomeView extends BaseComponent { 12 | 13 | ///////////////////////////////////////////////////////// 14 | // 15 | // 16 | ///////////////////////////////////////////////////////// 17 | constructor () { 18 | 19 | super () 20 | 21 | this.storageSvc = ServiceManager.getService( 22 | 'StorageSvc') 23 | 24 | this.modelSvc = ServiceManager.getService( 25 | 'ModelSvc') 26 | 27 | this.state = { 28 | search: '', 29 | items: [] 30 | } 31 | } 32 | 33 | ///////////////////////////////////////////////////////// 34 | // 35 | // 36 | ///////////////////////////////////////////////////////// 37 | async componentWillMount () { 38 | 39 | const items = this.storageSvc.load( 40 | 'configurator.models', []) 41 | 42 | this.assignState({ 43 | items 44 | }) 45 | 46 | this.modelSvc.getModels('configurator').then( 47 | (dbItems) => { 48 | 49 | const dbItemsStr = JSON.stringify(dbItems) 50 | const itemsStr = JSON.stringify(items) 51 | 52 | if (dbItemsStr !== itemsStr) { 53 | 54 | this.storageSvc.save( 55 | 'configurator.models', 56 | dbItems) 57 | 58 | this.assignState({ 59 | items: dbItems 60 | }) 61 | } 62 | }) 63 | } 64 | 65 | ///////////////////////////////////////////////////////// 66 | // 67 | // 68 | ///////////////////////////////////////////////////////// 69 | gotToLink (e, href) { 70 | 71 | e.preventDefault() 72 | 73 | browserHistory.push(href) 74 | } 75 | 76 | ///////////////////////////////////////////////////////// 77 | // 78 | // 79 | ///////////////////////////////////////////////////////// 80 | onKeyDown (e) { 81 | 82 | if (e.keyCode === 13) { 83 | 84 | e.stopPropagation() 85 | e.preventDefault() 86 | } 87 | } 88 | 89 | ///////////////////////////////////////////////////////// 90 | // 91 | // 92 | ///////////////////////////////////////////////////////// 93 | onSearchChanged (e) { 94 | 95 | this.assignState({ 96 | search: e.target.value.toLowerCase() 97 | }) 98 | } 99 | 100 | ///////////////////////////////////////////////////////// 101 | // 102 | // 103 | ///////////////////////////////////////////////////////// 104 | renderItems () { 105 | 106 | const {search, items} = this.state 107 | 108 | const filteredItems = items.filter((model) => { 109 | return search.length 110 | ? model.name.toLowerCase().indexOf(search) > -1 111 | : true 112 | }) 113 | 114 | return filteredItems.map((item) => { 115 | 116 | return 117 | }) 118 | } 119 | 120 | ///////////////////////////////////////////////////////// 121 | // 122 | // 123 | ///////////////////////////////////////////////////////// 124 | render () { 125 | 126 | return ( 127 |
128 | this.onSearchChanged(e)} 130 | onKeyDown={(e) => this.onKeyDown(e)} 131 | data-placeholder="Search ..." 132 | html={this.state.search} 133 | className="search" 134 | /> 135 |
136 |
137 |
138 | {this.renderItems()} 139 |
140 |
141 |
142 |
143 | ) 144 | } 145 | } 146 | 147 | export default ConfiguratorHomeView 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /src/routes/Configurator/ConfiguratorView/ConfiguratorHomeView/ConfiguratorItem.js: -------------------------------------------------------------------------------- 1 | import { DropdownButton, MenuItem } from 'react-bootstrap' 2 | import BaseComponent from 'BaseComponent' 3 | import ServiceManager from 'SvcManager' 4 | import { Link } from 'react-router' 5 | import Stars from 'react-stars' 6 | import Image from 'Image' 7 | import React from 'react' 8 | import Label from 'Label' 9 | 10 | class ConfiguratorItem extends BaseComponent { 11 | 12 | ///////////////////////////////////////////////////////// 13 | // 14 | // 15 | ///////////////////////////////////////////////////////// 16 | constructor () { 17 | 18 | super () 19 | 20 | this.state = { 21 | activeModel: null 22 | } 23 | } 24 | 25 | ///////////////////////////////////////////////////////// 26 | // 27 | // 28 | ///////////////////////////////////////////////////////// 29 | setActiveModel (activeModel) { 30 | 31 | this.assignState({ 32 | activeModel 33 | }) 34 | } 35 | 36 | ///////////////////////////////////////////////////////// 37 | // 38 | // 39 | ///////////////////////////////////////////////////////// 40 | componentWillMount () { 41 | 42 | const item = this.props.item 43 | 44 | const name = !item.model 45 | ? (item.displayName || item.name) 46 | : item.model.name 47 | 48 | const activeModel = { 49 | _id: item._id, 50 | name 51 | } 52 | 53 | this.assignState({ 54 | activeModel 55 | }) 56 | 57 | if (item.extraModels) { 58 | 59 | const extraModels = [ 60 | ...item.extraModels 61 | ] 62 | 63 | extraModels.unshift(activeModel) 64 | 65 | this.assignState({ 66 | extraModels, 67 | activeModel 68 | }) 69 | } 70 | } 71 | 72 | ///////////////////////////////////////////////////////// 73 | // 74 | // 75 | ///////////////////////////////////////////////////////// 76 | ratingChanged () { 77 | 78 | } 79 | 80 | ///////////////////////////////////////////////////////// 81 | // 82 | // 83 | ///////////////////////////////////////////////////////// 84 | renderDropdown () { 85 | 86 | const extraModels = this.state.extraModels 87 | 88 | const menuItems = extraModels.map((model, idx) => { 89 | return ( 90 | { 91 | 92 | this.setActiveModel(model) 93 | }}> 94 | { model.name } 95 | 96 | ) 97 | }) 98 | 99 | return ( 100 | 104 | { menuItems } 105 | 106 | ) 107 | } 108 | 109 | ///////////////////////////////////////////////////////// 110 | // 111 | // 112 | ///////////////////////////////////////////////////////// 113 | render () { 114 | 115 | const activeModel = this.state.activeModel 116 | 117 | const item = this.props.item 118 | 119 | const href = `/configurator?id=${activeModel._id}` 120 | 121 | const thumbnailUrl = 122 | `/resources/img/configurator/${item.name}.png` 123 | 124 | return ( 125 |
126 | 127 | 130 |
131 | 132 |
133 |

134 | { item.desc || '' } 135 |

136 | 137 |
138 | { 139 | item.extraModels && 140 | this.renderDropdown() 141 | } 142 |
143 |
149 |
150 |
158 |
159 |
160 | ) 161 | } 162 | } 163 | 164 | export default ConfiguratorItem 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /src/routes/Configurator/ConfiguratorView/ConfiguratorHomeView/index.js: -------------------------------------------------------------------------------- 1 | import ConfiguratorHomeView from './ConfiguratorHomeView' 2 | 3 | export default ConfiguratorHomeView 4 | -------------------------------------------------------------------------------- /src/routes/Configurator/ConfiguratorView/ConfiguratorView.js: -------------------------------------------------------------------------------- 1 | import ConfiguratorHomeView from './ConfiguratorHomeView' 2 | import ViewerConfigurator from 'Viewer.Configurator' 3 | import { browserHistory } from 'react-router' 4 | import './ConfiguratorView.scss' 5 | import React from 'react' 6 | 7 | class ConfiguratorView extends React.Component { 8 | 9 | ///////////////////////////////////////////////////////// 10 | // 11 | // 12 | ///////////////////////////////////////////////////////// 13 | constructor (props) { 14 | 15 | super (props) 16 | 17 | this.onError = this.onError.bind(this) 18 | } 19 | 20 | ///////////////////////////////////////////////////////// 21 | // 22 | // 23 | ///////////////////////////////////////////////////////// 24 | componentWillMount () { 25 | 26 | this.props.setNavbarState({ 27 | links: { 28 | settings: false 29 | } 30 | }) 31 | } 32 | 33 | ///////////////////////////////////////////////////////// 34 | // 35 | // 36 | ///////////////////////////////////////////////////////// 37 | onError (error) { 38 | 39 | if (error.status === 404) { 40 | 41 | browserHistory.push('/404') 42 | 43 | } else if (error) { 44 | 45 | console.log('unhandled error:') 46 | console.log(error) 47 | } 48 | } 49 | 50 | ///////////////////////////////////////////////////////// 51 | // 52 | // 53 | ///////////////////////////////////////////////////////// 54 | render () { 55 | 56 | const view = this.props.location.query.id 57 | ? 68 | : 69 | 70 | return ( 71 |
72 | { view } 73 |
74 | ) 75 | } 76 | } 77 | 78 | export default ConfiguratorView 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/routes/Configurator/ConfiguratorView/ConfiguratorView.scss: -------------------------------------------------------------------------------- 1 | 2 | .configurator-view { 3 | height: calc(100vh - 69px); 4 | background-color: #dadada; 5 | position: relative; 6 | width: 100vw; 7 | } 8 | 9 | .configurator-toolbar { 10 | position: absolute; 11 | bottom: 100px; 12 | right: 105px; 13 | } 14 | 15 | .configurator-toolbar #toolbar-markup3D .adsk-control-tooltip { 16 | transform: translate(-65px, 25px); 17 | } 18 | 19 | .configurator-toolbar #toolbar-transform .adsk-control-tooltip { 20 | transform: translate(-140px, 20px); 21 | } 22 | 23 | .configurator-toolbar #toolbar-translate .adsk-control-tooltip { 24 | transform: translate(-125px, -20px); 25 | } 26 | 27 | .configurator-toolbar #toolbar-rotate .adsk-control-tooltip { 28 | transform: translate(-115px, -15px); 29 | } 30 | -------------------------------------------------------------------------------- /src/routes/Configurator/ConfiguratorView/index.js: -------------------------------------------------------------------------------- 1 | import ConfiguratorView from './ConfiguratorView' 2 | 3 | export default ConfiguratorView 4 | -------------------------------------------------------------------------------- /src/routes/Configurator/containers/ConfiguratorContainer.js: -------------------------------------------------------------------------------- 1 | import ConfiguratorView from '../ConfiguratorView' 2 | import { connect } from 'react-redux' 3 | 4 | import { 5 | setNavbarState, 6 | setViewerEnv 7 | } from '../../../store/app' 8 | 9 | const mapDispatchToProps = { 10 | setNavbarState, 11 | setViewerEnv 12 | } 13 | 14 | const mapDispatchToProps = { 15 | 16 | } 17 | 18 | const mapStateToProps = (state) => ( 19 | Object.assign({}, state.configurator, { 20 | appState: state.app 21 | }) 22 | ) 23 | 24 | export default connect( 25 | mapStateToProps, 26 | mapDispatchToProps)(ConfiguratorView) 27 | -------------------------------------------------------------------------------- /src/routes/Configurator/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | 5 | path : 'configurator', 6 | 7 | getComponent (nextState, cb) { 8 | 9 | require.ensure([], (require) => { 10 | 11 | const container = require('./containers/ConfiguratorContainer').default 12 | const reducer = require('./modules/configurator').default 13 | 14 | injectReducer(store, { key: 'configurator', reducer }) 15 | 16 | cb(null, container) 17 | 18 | }, 'configurator') 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /src/routes/Configurator/modules/configurator.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | 5 | // ------------------------------------ 6 | // Actions 7 | // ------------------------------------ 8 | 9 | export const actions = { 10 | 11 | } 12 | 13 | // ------------------------------------ 14 | // Action Handlers 15 | // ------------------------------------ 16 | const ACTION_HANDLERS = { 17 | 18 | } 19 | 20 | // ------------------------------------ 21 | // Reducer 22 | // ------------------------------------ 23 | const initialState = { 24 | 25 | } 26 | 27 | export default function reducer (state = initialState, action) { 28 | 29 | const handler = ACTION_HANDLERS[action.type] 30 | 31 | return handler ? handler(state, action) : state 32 | } 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/routes/Counter/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | export const Counter = ({ counter, increment, doubleAsync }) => ( 5 |
6 |

Counter: {counter}

7 | 10 | {' '} 11 | 14 |
15 | ) 16 | Counter.propTypes = { 17 | counter: PropTypes.number.isRequired, 18 | increment: PropTypes.func.isRequired, 19 | doubleAsync: PropTypes.func.isRequired, 20 | } 21 | 22 | export default Counter 23 | -------------------------------------------------------------------------------- /src/routes/Counter/containers/CounterContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { increment, doubleAsync } from '../modules/counter' 3 | 4 | /* This is a container component. Notice it does not contain any JSX, 5 | nor does it import React. This component is **only** responsible for 6 | wiring in the actions and state necessary to render a presentational 7 | component - in this case, the counter: */ 8 | 9 | import Counter from '../components/Counter' 10 | 11 | /* Object of action creators (can also be function that returns object). 12 | Keys will be passed as props to presentational components. Here we are 13 | implementing our wrapper around increment; the component doesn't care */ 14 | 15 | const mapDispatchToProps = { 16 | increment : () => increment(1), 17 | doubleAsync 18 | } 19 | 20 | const mapStateToProps = (state) => ({ 21 | counter : state.counter 22 | }) 23 | 24 | /* Note: mapStateToProps is where you should use `reselect` to create selectors, ie: 25 | 26 | import { createSelector } from 'reselect' 27 | const counter = (state) => state.counter 28 | const tripleCount = createSelector(counter, (count) => count * 3) 29 | const mapStateToProps = (state) => ({ 30 | counter: tripleCount(state) 31 | }) 32 | 33 | Selectors can compute derived data, allowing Redux to store the minimal possible state. 34 | Selectors are efficient. A selector is not recomputed unless one of its arguments change. 35 | Selectors are composable. They can be used as input to other selectors. 36 | https://github.com/reactjs/reselect */ 37 | 38 | export default connect(mapStateToProps, mapDispatchToProps)(Counter) 39 | -------------------------------------------------------------------------------- /src/routes/Counter/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | path : '/', 5 | /* Async getComponent is only invoked when route matches */ 6 | getComponent (nextState, cb) { 7 | /* Webpack - use 'require.ensure' to create a split point 8 | and embed an async module loader (jsonp) when bundling */ 9 | require.ensure([], (require) => { 10 | /* Webpack - use require callback to define 11 | dependencies for bundling */ 12 | const Counter = require('./containers/CounterContainer').default 13 | const reducer = require('./modules/counter').default 14 | 15 | /* Add the reducer to the store on key 'counter' */ 16 | injectReducer(store, { key: 'counter', reducer }) 17 | 18 | /* Return getComponent */ 19 | cb(null, Counter) 20 | 21 | /* Webpack named bundle */ 22 | }, 'counter') 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/routes/Counter/modules/counter.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const COUNTER_INCREMENT = 'COUNTER_INCREMENT' 5 | export const COUNTER_DOUBLE_ASYNC = 'COUNTER_DOUBLE_ASYNC' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export function increment (value = 1) { 11 | return { 12 | type : COUNTER_INCREMENT, 13 | payload : value 14 | } 15 | } 16 | 17 | /* This is a thunk, meaning it is a function that immediately 18 | returns a function for lazy evaluation. It is incredibly useful for 19 | creating async actions, especially when combined with redux-thunk! */ 20 | 21 | export const doubleAsync = () => { 22 | return (dispatch, getState) => { 23 | return new Promise((resolve) => { 24 | setTimeout(() => { 25 | dispatch({ 26 | type : COUNTER_DOUBLE_ASYNC, 27 | payload : getState().counter 28 | }) 29 | resolve() 30 | }, 200) 31 | }) 32 | } 33 | } 34 | 35 | export const actions = { 36 | increment, 37 | doubleAsync 38 | } 39 | 40 | // ------------------------------------ 41 | // Action Handlers 42 | // ------------------------------------ 43 | const ACTION_HANDLERS = { 44 | [COUNTER_INCREMENT] : (state, action) => state + action.payload, 45 | [COUNTER_DOUBLE_ASYNC] : (state, action) => state * 2 46 | } 47 | 48 | // ------------------------------------ 49 | // Reducer 50 | // ------------------------------------ 51 | const initialState = 0 52 | export default function counterReducer (state = initialState, action) { 53 | const handler = ACTION_HANDLERS[action.type] 54 | 55 | return handler ? handler(state, action) : state 56 | } 57 | -------------------------------------------------------------------------------- /src/routes/Home/assets/Duck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STEMLab/InViewer/f03df4cbdea47317ee2dc333b9869a05cfe6b6e1/src/routes/Home/assets/Duck.jpg -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DuckImage from '../assets/Duck.jpg' 3 | import './HomeView.scss' 4 | 5 | export const HomeView = () => ( 6 |
7 |

Welcome!

8 | This is a duck, because Redux! 9 |
10 | ) 11 | 12 | export default HomeView 13 | -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.scss: -------------------------------------------------------------------------------- 1 | .duck { 2 | display: block; 3 | width: 120px; 4 | margin: 1.5rem auto; 5 | } 6 | -------------------------------------------------------------------------------- /src/routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomeView from './components/HomeView' 2 | 3 | // Sync route definition 4 | export default { 5 | component : HomeView 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/Main/components/Main.js: -------------------------------------------------------------------------------- 1 | import { browserHistory } from 'react-router' 2 | import './Main.scss' 3 | import React from 'react' 4 | import ViewerContainer from 'Viewer.Container' 5 | 6 | export default class MainView extends React.Component { 7 | 8 | ///////////////////////////////////////////////////////// 9 | // 10 | // 11 | ///////////////////////////////////////////////////////// 12 | constructor (props) { 13 | 14 | super (props) 15 | 16 | this.onError = this.onError.bind(this) 17 | } 18 | 19 | ///////////////////////////////////////////////////////// 20 | // 21 | // 22 | ///////////////////////////////////////////////////////// 23 | componentWillMount () { 24 | 25 | /* 26 | this.props.setNavbarState({ 27 | links: { 28 | settings: false 29 | } 30 | }) 31 | */ 32 | 33 | } 34 | 35 | ///////////////////////////////////////////////////////// 36 | // 37 | // 38 | ///////////////////////////////////////////////////////// 39 | onError = (error) => { 40 | 41 | if (error.status === 404) { 42 | 43 | browserHistory.push('/404') 44 | 45 | } else if (error) { 46 | 47 | console.log('unhandled error:') 48 | console.log(error) 49 | } 50 | } 51 | 52 | ///////////////////////////////////////////////////////// 53 | // 54 | // 55 | ///////////////////////////////////////////////////////// 56 | render () { 57 | 58 | /* 59 | const view = this.props.location.query.id 60 | ? 71 | : 72 | */ 73 | 74 | const view = 82 | 83 | return ( 84 |
85 | { view } 86 |
87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/routes/Main/components/Main.scss: -------------------------------------------------------------------------------- 1 | 2 | .main-view { 3 | height: calc(100vh - 69px); 4 | background-color: #dadada; 5 | position: relative; 6 | } 7 | 8 | .configurator-toolbar { 9 | position: absolute; 10 | bottom: 100px; 11 | right: 105px; 12 | } 13 | 14 | .configurator-toolbar #toolbar-markup3D .adsk-control-tooltip { 15 | transform: translate(-65px, 25px); 16 | } 17 | 18 | .configurator-toolbar #toolbar-transform .adsk-control-tooltip { 19 | transform: translate(-140px, 20px); 20 | } 21 | 22 | .configurator-toolbar #toolbar-translate .adsk-control-tooltip { 23 | transform: translate(-125px, -20px); 24 | } 25 | 26 | .configurator-toolbar #toolbar-rotate .adsk-control-tooltip { 27 | transform: translate(-115px, -15px); 28 | } 29 | -------------------------------------------------------------------------------- /src/routes/Main/containers/MainContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | /* This is a container component. Notice it does not contain any JSX, 4 | nor does it import React. This component is **only** responsible for 5 | wiring in the actions and state necessary to render a presentational 6 | component - in this case, the counter: */ 7 | 8 | import Main from '../components/Main' 9 | 10 | /* Object of action creators (can also be function that returns object). 11 | Keys will be passed as props to presentational components. Here we are 12 | implementing our wrapper around increment; the component doesn't care */ 13 | 14 | import { 15 | setMenubarState, 16 | setViewerEnv, 17 | onIGMLResponseSuccess 18 | } from '../../../store/app' 19 | 20 | const mapDispatchToProps = { 21 | setMenubarState, 22 | setViewerEnv, 23 | onIGMLResponseSuccess 24 | } 25 | 26 | const mapStateToProps = (state) => ( 27 | Object.assign({}, state.main, { 28 | appState: state.app 29 | }) 30 | ) 31 | 32 | /* Note: mapStateToProps is where you should use `reselect` to create selectors, ie: 33 | 34 | import { createSelector } from 'reselect' 35 | const counter = (state) => state.counter 36 | const tripleCount = createSelector(counter, (count) => count * 3) 37 | const mapStateToProps = (state) => ({ 38 | counter: tripleCount(state) 39 | }) 40 | 41 | Selectors can compute derived data, allowing Redux to store the minimal possible state. 42 | Selectors are efficient. A selector is not recomputed unless one of its arguments change. 43 | Selectors are composable. They can be used as input to other selectors. 44 | https://github.com/reactjs/reselect */ 45 | 46 | export default connect(mapStateToProps, mapDispatchToProps)(Main) 47 | -------------------------------------------------------------------------------- /src/routes/Main/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | 5 | /* Async getComponent is only invoked when route matches */ 6 | getComponent (nextState, cb) { 7 | /* Webpack - use 'require.ensure' to create a split point 8 | and embed an async module loader (jsonp) when bundling */ 9 | require.ensure([], (require) => { 10 | /* Webpack - use require callback to define 11 | dependencies for bundling */ 12 | const Main = require('./containers/MainContainer').default 13 | const reducer = require('./modules/main').default 14 | 15 | /* Add the reducer to the store on key 'counter' */ 16 | injectReducer(store, { key: 'main', reducer }) 17 | 18 | /* Return getComponent */ 19 | cb(null, Main) 20 | 21 | /* Webpack named bundle */ 22 | }, 'main') 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/routes/Main/modules/main.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const COUNTER_INCREMENT = 'COUNTER_INCREMENT' 5 | export const COUNTER_DOUBLE_ASYNC = 'COUNTER_DOUBLE_ASYNC' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export function increment (value = 1) { 11 | return { 12 | type : COUNTER_INCREMENT, 13 | payload : value 14 | } 15 | } 16 | 17 | /* This is a thunk, meaning it is a function that immediately 18 | returns a function for lazy evaluation. It is incredibly useful for 19 | creating async actions, especially when combined with redux-thunk! */ 20 | 21 | export const doubleAsync = () => { 22 | return (dispatch, getState) => { 23 | return new Promise((resolve) => { 24 | setTimeout(() => { 25 | dispatch({ 26 | type : COUNTER_DOUBLE_ASYNC, 27 | payload : getState().counter 28 | }) 29 | resolve() 30 | }, 200) 31 | }) 32 | } 33 | } 34 | 35 | export const actions = { 36 | increment, 37 | doubleAsync 38 | } 39 | 40 | // ------------------------------------ 41 | // Action Handlers 42 | // ------------------------------------ 43 | const ACTION_HANDLERS = { 44 | [COUNTER_INCREMENT] : (state, action) => state + action.payload, 45 | [COUNTER_DOUBLE_ASYNC] : (state, action) => state * 2 46 | } 47 | 48 | // ------------------------------------ 49 | // Reducer 50 | // ------------------------------------ 51 | const initialState = 0 52 | export default function counterReducer (state = initialState, action) { 53 | const handler = ACTION_HANDLERS[action.type] 54 | 55 | return handler ? handler(state, action) : state 56 | } 57 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | // We only need to import the modules necessary for initial render 2 | import CoreLayout from '../layouts/CoreLayout' 3 | import Main from './Main' 4 | /* Note: Instead of using JSX, we recommend using react-router 5 | PlainRoute objects to build route definitions. */ 6 | 7 | export const createRoutes = (store) => ({ 8 | defaultRoute : Main(store), 9 | indexRoute : Main(store), 10 | path : '/', 11 | component : CoreLayout 12 | }) 13 | 14 | /* Note: childRoutes can be chunked or otherwise loaded programmatically 15 | using getChildRoutes with the following signature: 16 | 17 | getChildRoutes (location, cb) { 18 | require.ensure([], (require) => { 19 | cb(null, [ 20 | // Remove imports! 21 | require('./Counter').default(store) 22 | ]) 23 | }) 24 | } 25 | 26 | However, this is not necessary for code-splitting! It simply provides 27 | an API for async route definitions. Your code splitting should occur 28 | inside the route `getComponent` function, since it is only invoked 29 | when the route exists and matches. 30 | */ 31 | 32 | export default createRoutes 33 | -------------------------------------------------------------------------------- /src/services/BaseSvc.js: -------------------------------------------------------------------------------- 1 | import ServiceManager from './SvcManager' 2 | import EventsEmitter from 'EventsEmitter' 3 | 4 | export default class BaseSvc extends EventsEmitter { 5 | 6 | ///////////////////////////////////////////////////////////////// 7 | // 8 | // 9 | ///////////////////////////////////////////////////////////////// 10 | constructor(config = {}) { 11 | 12 | super() 13 | 14 | this._config = config 15 | } 16 | 17 | ///////////////////////////////////////////////////////////////// 18 | // 19 | // 20 | ///////////////////////////////////////////////////////////////// 21 | name() { 22 | 23 | return this._name 24 | } 25 | 26 | ///////////////////////////////////////////////////////////////// 27 | // 28 | // 29 | ///////////////////////////////////////////////////////////////// 30 | config() { 31 | 32 | return this._config 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/services/DialogSvc.js: -------------------------------------------------------------------------------- 1 | import ServiceDlg from 'Dialogs/ServiceDlg' 2 | import BaseSvc from './BaseSvc' 3 | import React from 'react' 4 | 5 | export default class DialogSvc extends BaseSvc { 6 | 7 | ///////////////////////////////////////////////////////////////// 8 | // 9 | // 10 | ///////////////////////////////////////////////////////////////// 11 | constructor (opts) { 12 | 13 | super (opts) 14 | 15 | this.disableOK = this.disableOK.bind(this) 16 | this.close = this.close.bind(this) 17 | } 18 | 19 | ///////////////////////////////////////////////////////////////// 20 | // 21 | // 22 | ///////////////////////////////////////////////////////////////// 23 | setComponent (component) { 24 | 25 | this.component = component 26 | 27 | this.setState ({ 28 | close: this.close, 29 | open: false 30 | }) 31 | } 32 | 33 | ///////////////////////////////////////////////////////////////// 34 | // 35 | // 36 | ///////////////////////////////////////////////////////////////// 37 | setState (state, assign = false) { 38 | 39 | return new Promise ((resolve) => { 40 | 41 | const dialogSvcState = assign 42 | ? Object.assign({}, this.getState(), state) 43 | : Object.assign({}, state, { 44 | close: this.close 45 | }) 46 | 47 | const componentState = Object.assign({}, 48 | this.component.state, { 49 | dialogSvc: dialogSvcState 50 | }) 51 | 52 | this.component.setState(componentState, () => { 53 | 54 | resolve() 55 | }) 56 | }) 57 | } 58 | 59 | ///////////////////////////////////////////////////////////////// 60 | // 61 | // 62 | ///////////////////////////////////////////////////////////////// 63 | disableOK (disable) { 64 | 65 | this.setState ({ 66 | disableOK: disable 67 | }, true) 68 | } 69 | 70 | ///////////////////////////////////////////////////////////////// 71 | // 72 | // 73 | ///////////////////////////////////////////////////////////////// 74 | getState () { 75 | 76 | return this.component.state.dialogSvc 77 | } 78 | 79 | ///////////////////////////////////////////////////////////////// 80 | // 81 | // 82 | ///////////////////////////////////////////////////////////////// 83 | name() { 84 | 85 | return 'DialogSvc' 86 | } 87 | 88 | ///////////////////////////////////////////////////////////////// 89 | // 90 | // 91 | ///////////////////////////////////////////////////////////////// 92 | close (result) { 93 | 94 | this.setState ({ 95 | 96 | open: false 97 | 98 | }, false).then(() => { 99 | 100 | this.emit('dialog.close', result) 101 | }) 102 | } 103 | 104 | ///////////////////////////////////////////////////////////////// 105 | // 106 | // 107 | ///////////////////////////////////////////////////////////////// 108 | render() { 109 | 110 | const state = this.getState() 111 | 112 | return 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/services/EventSvc.js: -------------------------------------------------------------------------------- 1 | 2 | import BaseSvc from './BaseSvc' 3 | 4 | export default class EventSvc extends BaseSvc { 5 | 6 | ///////////////////////////////////////////////////////////////// 7 | // 8 | // 9 | ///////////////////////////////////////////////////////////////// 10 | constructor (config) { 11 | 12 | super (config) 13 | } 14 | 15 | ///////////////////////////////////////////////////////////////// 16 | // 17 | // 18 | ///////////////////////////////////////////////////////////////// 19 | name() { 20 | 21 | return 'EventSvc' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/services/NotifySvc.js: -------------------------------------------------------------------------------- 1 | 2 | import BaseSvc from './BaseSvc' 3 | 4 | export default class NotifySvc extends BaseSvc { 5 | 6 | ///////////////////////////////////////////////////////// 7 | // 8 | // 9 | ///////////////////////////////////////////////////////// 10 | constructor (config) { 11 | 12 | super (config) 13 | 14 | this.noticationMap = {} 15 | } 16 | 17 | ///////////////////////////////////////////////////////// 18 | // 19 | // 20 | ///////////////////////////////////////////////////////// 21 | initialize (notify) { 22 | 23 | this.notify = notify 24 | } 25 | 26 | ///////////////////////////////////////////////////////// 27 | // 28 | // 29 | ///////////////////////////////////////////////////////// 30 | name() { 31 | 32 | return 'NotifySvc' 33 | } 34 | 35 | ///////////////////////////////////////////////////////// 36 | // 37 | // 38 | ///////////////////////////////////////////////////////// 39 | getNotification (notificationId) { 40 | 41 | return this.noticationMap[notificationId] || null 42 | } 43 | 44 | ///////////////////////////////////////////////////////// 45 | // 46 | // 47 | ///////////////////////////////////////////////////////// 48 | remove (notification) { 49 | 50 | this.notify.remove (notification) 51 | 52 | if (this.noticationMap[notification.id]) { 53 | 54 | delete this.noticationMap[notification.id] 55 | } 56 | } 57 | 58 | ///////////////////////////////////////////////////////// 59 | // 60 | // 61 | ///////////////////////////////////////////////////////// 62 | update (notification) { 63 | 64 | this.notify.update (notification) 65 | } 66 | 67 | ///////////////////////////////////////////////////////// 68 | // 69 | // 70 | ///////////////////////////////////////////////////////// 71 | add (notificationInfo) { 72 | 73 | const notification = this.notify.add (notificationInfo) 74 | 75 | this.noticationMap[notification.id] = notification 76 | 77 | return notification 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/services/SocketSvc.js: -------------------------------------------------------------------------------- 1 | 2 | import ioClient from 'socket.io-client' 3 | import BaseSvc from './BaseSvc' 4 | 5 | export default class SocketSvc extends BaseSvc { 6 | 7 | ///////////////////////////////////////////////////////// 8 | // 9 | // 10 | ///////////////////////////////////////////////////////// 11 | constructor (config) { 12 | 13 | super (config) 14 | 15 | this.eventBuffer = [] 16 | } 17 | 18 | ///////////////////////////////////////////////////////// 19 | // 20 | // 21 | ///////////////////////////////////////////////////////// 22 | name () { 23 | 24 | return 'SocketSvc' 25 | } 26 | 27 | ///////////////////////////////////////////////////////// 28 | // 29 | // 30 | ///////////////////////////////////////////////////////// 31 | getSocketId () { 32 | 33 | return new Promise((resolve) => { 34 | 35 | if (this.socket) { 36 | 37 | return resolve(this.socket.id) 38 | } 39 | 40 | this.connect().then((socket) => { 41 | 42 | return resolve(socket.id) 43 | }) 44 | }) 45 | } 46 | 47 | ///////////////////////////////////////////////////////// 48 | // Socket Connection handler 49 | // 50 | ///////////////////////////////////////////////////////// 51 | connect () { 52 | 53 | return new Promise((resolve, reject) => { 54 | 55 | if (this.socket) { 56 | 57 | return resolve(this.socket) 58 | } 59 | 60 | this.socket = ioClient.connect( 61 | `${this._config.host}:${this._config.port}`, { 62 | reconnect: true 63 | }) 64 | 65 | this.socket.on('connect', () => { 66 | 67 | console.log('Socket connected: ' + this.socket.id) 68 | 69 | this.eventBuffer.forEach((event) => { 70 | 71 | this.socket.on(event.msgId, event.handler) 72 | }) 73 | 74 | resolve(this.socket) 75 | }) 76 | }) 77 | } 78 | 79 | ///////////////////////////////////////////////////////// 80 | // 81 | // 82 | ///////////////////////////////////////////////////////// 83 | on (msgIds, handler) { 84 | 85 | msgIds.split(' ').forEach((msgId) => { 86 | 87 | this.eventBuffer.push({ 88 | handler, 89 | msgId 90 | }) 91 | 92 | if (this.socket) { 93 | 94 | this.socket.on (msgId, handler) 95 | } 96 | }) 97 | } 98 | 99 | ///////////////////////////////////////////////////////// 100 | // 101 | // 102 | ///////////////////////////////////////////////////////// 103 | off (msgIds, handler) { 104 | 105 | msgIds.split(' ').forEach((msgId) => { 106 | 107 | this.eventBuffer = 108 | this.eventBuffer.filter((event) => { 109 | return (event.msgId !== msgId) 110 | }) 111 | 112 | if (this.socket) { 113 | 114 | this.socket.off (msgId, handler) 115 | } 116 | }) 117 | } 118 | 119 | ///////////////////////////////////////////////////////// 120 | // 121 | // 122 | ///////////////////////////////////////////////////////// 123 | async emit (msgId, msg) { 124 | 125 | if (this.socket) { 126 | 127 | this.socket.emit (msgId, msg) 128 | } 129 | } 130 | 131 | ///////////////////////////////////////////////////////// 132 | // 133 | // 134 | ///////////////////////////////////////////////////////// 135 | async broadcast (msgId, msg, filter = null) { 136 | 137 | if (this.socket) { 138 | 139 | this.socket.emit('broadcast', { 140 | filter, 141 | msgId, 142 | msg 143 | }) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/services/StorageSvc.js: -------------------------------------------------------------------------------- 1 | 2 | import BaseSvc from './BaseSvc' 3 | import Lockr from 'lockr' 4 | 5 | export default class StorageSvc extends BaseSvc { 6 | 7 | ///////////////////////////////////////////////////////// 8 | // 9 | // 10 | ///////////////////////////////////////////////////////// 11 | constructor (config) { 12 | 13 | super (config) 14 | 15 | this.currentStorageVersion = config.storageVersion 16 | } 17 | 18 | ///////////////////////////////////////////////////////// 19 | // 20 | // 21 | ///////////////////////////////////////////////////////// 22 | name () { 23 | 24 | return 'StorageSvc' 25 | } 26 | 27 | ///////////////////////////////////////////////////////// 28 | // 29 | // 30 | ///////////////////////////////////////////////////////// 31 | getStoragekey (key) { 32 | 33 | return `${this._config.storageKey}.${key}` 34 | } 35 | 36 | ///////////////////////////////////////////////////////// 37 | // 38 | // 39 | ///////////////////////////////////////////////////////// 40 | save (key, data) { 41 | 42 | const item = Array.isArray(data) 43 | ? {__array: data} 44 | : data 45 | 46 | const versionItem = Object.assign({}, item, { 47 | storageVersion: this.currentStorageVersion 48 | }) 49 | 50 | Lockr.set(this.getStoragekey(key), versionItem) 51 | } 52 | 53 | ///////////////////////////////////////////////////////// 54 | // 55 | // 56 | ///////////////////////////////////////////////////////// 57 | load (key, defaultValue = {}) { 58 | 59 | const storageKey = this.getStoragekey(key) 60 | 61 | const item = Lockr.get(storageKey) || defaultValue 62 | 63 | if (item.storageVersion) { 64 | 65 | if (this.currentStorageVersion > item.storageVersion) { 66 | return defaultValue 67 | } 68 | 69 | return item.__array || item 70 | } 71 | 72 | return defaultValue 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/services/SvcManager.js: -------------------------------------------------------------------------------- 1 | 2 | class SvcManager { 3 | 4 | ///////////////////////////////////////////////////////////////// 5 | // 6 | // 7 | ///////////////////////////////////////////////////////////////// 8 | constructor () { 9 | 10 | this._services = {} 11 | } 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | // 15 | // 16 | ///////////////////////////////////////////////////////////////// 17 | registerService (svc) { 18 | 19 | this._services[svc.name()] = svc 20 | } 21 | 22 | ///////////////////////////////////////////////////////////////// 23 | // 24 | // 25 | ///////////////////////////////////////////////////////////////// 26 | getService (name) { 27 | 28 | if (this._services[name]) { 29 | 30 | return this._services[name] 31 | } 32 | 33 | return null 34 | } 35 | } 36 | 37 | var TheSvcManager = new SvcManager() 38 | 39 | export default TheSvcManager 40 | -------------------------------------------------------------------------------- /src/store/app.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const SET_MENUBAR_STATE = 'SET_MENUBAR_STATE' 5 | export const SET_VIEWER_ENV = 'SET_VIEWER_ENV' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export function setMenubarState (state) { 11 | return { 12 | type : SET_MENUBAR_STATE, 13 | payload : state 14 | } 15 | } 16 | 17 | export function setViewerEnv (env) { 18 | return { 19 | type : SET_VIEWER_ENV, 20 | payload : env 21 | } 22 | } 23 | 24 | // ------------------------------------ 25 | // Action Handlers 26 | // ------------------------------------ 27 | const ACTION_HANDLERS = { 28 | [SET_MENUBAR_STATE] : (state, action) => { 29 | 30 | const menubar = merge({}, 31 | state.menubar, 32 | action.payload) 33 | 34 | return Object.assign({}, state, { 35 | menubar 36 | }) 37 | }, 38 | [SET_VIEWER_ENV] : (state, action) => { 39 | 40 | return Object.assign({}, state, { 41 | viewerEnv: action.payload 42 | }) 43 | } 44 | } 45 | 46 | // ------------------------------------ 47 | // Initial App State 48 | // ------------------------------------ 49 | const createInitialState = () => { 50 | 51 | const defaultAppState = { 52 | layoutType: 'left' 53 | } 54 | 55 | const defaultState = { 56 | menubar: { 57 | visible: true, 58 | links:{ 59 | 60 | } 61 | }, 62 | viewerEnv: { 63 | 64 | }, 65 | response: null 66 | } 67 | 68 | const initialState = Object.assign({}, 69 | defaultState, {storage: defaultAppState}) 70 | 71 | return initialState 72 | } 73 | 74 | // ------------------------------------ 75 | // Reducer 76 | // ------------------------------------ 77 | 78 | export default function reducer ( 79 | state = createInitialState(), action) { 80 | 81 | const handler = ACTION_HANDLERS[action.type] 82 | 83 | return handler 84 | ? handler(state, action) 85 | : state 86 | } 87 | -------------------------------------------------------------------------------- /src/store/createStore.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore as createReduxStore } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import { browserHistory } from 'react-router' 4 | import makeRootReducer from './reducers' 5 | import { updateLocation } from './location' 6 | 7 | const createStore = (initialState = {}) => { 8 | // ====================================================== 9 | // Middleware Configuration 10 | // ====================================================== 11 | const middleware = [thunk] 12 | 13 | // ====================================================== 14 | // Store Enhancers 15 | // ====================================================== 16 | const enhancers = [] 17 | let composeEnhancers = compose 18 | 19 | if (__DEV__) { 20 | if (typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function') { 21 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 22 | } 23 | } 24 | 25 | // ====================================================== 26 | // Store Instantiation and HMR Setup 27 | // ====================================================== 28 | const store = createReduxStore( 29 | makeRootReducer(), 30 | initialState, 31 | composeEnhancers( 32 | applyMiddleware(...middleware), 33 | ...enhancers 34 | ) 35 | ) 36 | store.asyncReducers = {} 37 | 38 | // To unsubscribe, invoke `store.unsubscribeHistory()` anytime 39 | store.unsubscribeHistory = browserHistory.listen(updateLocation(store)) 40 | 41 | if (module.hot) { 42 | module.hot.accept('./reducers', () => { 43 | const reducers = require('./reducers').default 44 | store.replaceReducer(reducers(store.asyncReducers)) 45 | }) 46 | } 47 | 48 | return store 49 | } 50 | 51 | export default createStore 52 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import createStore from './createStore' 2 | 3 | //Services 4 | import ServiceManager from 'SvcManager' 5 | import NotifySvc from 'NotifySvc' 6 | import DialogSvc from 'DialogSvc' 7 | import EventSvc from 'EventSvc' 8 | import SocketSvc from 'SocketSvc' 9 | 10 | // ======================================================== 11 | // Services Initialization 12 | // ======================================================== 13 | const notifySvc = new NotifySvc() 14 | const dialogSvc = new DialogSvc() 15 | const eventSvc = new EventSvc() 16 | 17 | const socketSvc = new SocketSvc({ 18 | host: "127.0.0.1", 19 | port: "3000" 20 | }) 21 | 22 | ServiceManager.registerService(dialogSvc) 23 | ServiceManager.registerService(notifySvc) 24 | ServiceManager.registerService(eventSvc) 25 | ServiceManager.registerService(socketSvc) 26 | 27 | // ======================================================== 28 | // Store Instantiation 29 | // ======================================================== 30 | const initialState = window.___INITIAL_STATE__ 31 | 32 | const store = createStore(initialState) 33 | 34 | export default store 35 | -------------------------------------------------------------------------------- /src/store/location.js: -------------------------------------------------------------------------------- 1 | import browserHistory from 'react-router/lib/browserHistory' 2 | 3 | // ------------------------------------ 4 | // Constants 5 | // ------------------------------------ 6 | export const LOCATION_CHANGE = 'LOCATION_CHANGE' 7 | 8 | // ------------------------------------ 9 | // Actions 10 | // ------------------------------------ 11 | export function locationChange (location = '/') { 12 | return { 13 | type : LOCATION_CHANGE, 14 | payload : location 15 | } 16 | } 17 | 18 | // ------------------------------------ 19 | // Specialized Action Creator 20 | // ------------------------------------ 21 | export const updateLocation = ({ dispatch }) => { 22 | return (nextLocation) => dispatch(locationChange(nextLocation)) 23 | } 24 | 25 | // ------------------------------------ 26 | // Reducer 27 | // ------------------------------------ 28 | const initialState = browserHistory.getCurrentLocation() 29 | export default function locationReducer (state = initialState, action) { 30 | return action.type === LOCATION_CHANGE 31 | ? action.payload 32 | : state 33 | } 34 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { reducer as notificationsReducer } from 'reapop' 2 | import { combineReducers } from 'redux' 3 | 4 | //default reducers 5 | import locationReducer from './location' 6 | import appReducer from './app' 7 | 8 | export const makeRootReducer = (asyncReducers) => { 9 | return combineReducers({ 10 | notifications: notificationsReducer(), 11 | location: locationReducer, 12 | app: appReducer, 13 | ...asyncReducers 14 | }) 15 | } 16 | 17 | export const injectReducer = (store, { key, reducer }) => { 18 | if (Object.hasOwnProperty.call(store.asyncReducers, key)) return 19 | 20 | store.asyncReducers[key] = reducer 21 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 22 | } 23 | 24 | export default makeRootReducer 25 | -------------------------------------------------------------------------------- /src/styles/_base.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Application Settings Go Here 3 | ------------------------------------ 4 | This file acts as a bundler for all variables/mixins/themes, so they 5 | can easily be swapped out without `core.scss` ever having to know. 6 | 7 | For example: 8 | 9 | @import './variables/colors'; 10 | @import './variables/components'; 11 | @import './themes/default'; 12 | */ 13 | 14 | @import url(http://fonts.googleapis.com/earlyaccess/jejugothic.css); 15 | -------------------------------------------------------------------------------- /src/styles/core.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | // Some best-practice CSS that's useful for most apps 3 | // Just remove them if they're not what you want 4 | 5 | body { 6 | font-family: 'Jeju Gothic', serif !important; 7 | overflow: hidden; 8 | } 9 | 10 | html { 11 | box-sizing: border-box; 12 | overflow-style: none; 13 | overflow: hidden; 14 | } 15 | 16 | html, 17 | body { 18 | overflow: -moz-scrollbars-none; 19 | -ms-overflow-style: none; 20 | text-align: center; 21 | user-select: none; 22 | height: 100%; 23 | padding: 0; 24 | margin: 0; 25 | } 26 | 27 | *, 28 | *:before, 29 | *:after { 30 | box-sizing: inherit; 31 | } 32 | 33 | .core-layout__viewport { 34 | height: calc(100vh - 65px); 35 | } 36 | 37 | .container { 38 | padding: 0; 39 | margin: 0; 40 | width: 100%; 41 | } 42 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : "../.eslintrc", 3 | "env" : { 4 | "mocha" : true 5 | }, 6 | "globals" : { 7 | "expect" : false, 8 | "should" : false, 9 | "sinon" : false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/layouts/PageLayout.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PageLayout from 'layouts/PageLayout/PageLayout' 3 | import { shallow } from 'enzyme' 4 | 5 | describe('(Layout) PageLayout', () => { 6 | it('renders as a
', () => { 7 | shallow().should.have.tagName('div') 8 | }) 9 | 10 | it('renders a project title', () => { 11 | shallow().find('h1').should.have.text('React Redux Starter Kit') 12 | }) 13 | 14 | it('renders its children inside of the viewport', () => { 15 | const Child = () =>

child

16 | shallow( 17 | 18 | 19 | 20 | ) 21 | .find('.page-layout__viewport') 22 | .should.contain() 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/routes/Counter/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { Counter } from 'routes/Counter/components/Counter' 4 | import { shallow } from 'enzyme' 5 | 6 | describe('(Component) Counter', () => { 7 | let _props, _spies, _wrapper 8 | 9 | beforeEach(() => { 10 | _spies = {} 11 | _props = { 12 | counter : 5, 13 | ...bindActionCreators({ 14 | doubleAsync : (_spies.doubleAsync = sinon.spy()), 15 | increment : (_spies.increment = sinon.spy()) 16 | }, _spies.dispatch = sinon.spy()) 17 | } 18 | _wrapper = shallow() 19 | }) 20 | 21 | it('renders as a
.', () => { 22 | expect(_wrapper.is('div')).to.equal(true) 23 | }) 24 | 25 | it('renders with an

that includes Counter label.', () => { 26 | expect(_wrapper.find('h2').text()).to.match(/Counter:/) 27 | }) 28 | 29 | it('renders {props.counter} at the end of the sample counter

.', () => { 30 | expect(_wrapper.find('h2').text()).to.match(/5$/) 31 | _wrapper.setProps({ counter: 8 }) 32 | expect(_wrapper.find('h2').text()).to.match(/8$/) 33 | }) 34 | 35 | it('renders exactly two buttons.', () => { 36 | expect(_wrapper.find('button')).to.have.length(2) 37 | }) 38 | 39 | describe('Increment', () => { 40 | let _button 41 | 42 | beforeEach(() => { 43 | _button = _wrapper.find('button').filterWhere(a => a.text() === 'Increment') 44 | }) 45 | 46 | it('exists', () => { 47 | expect(_button).to.exist() 48 | }) 49 | 50 | it('is a primary button', () => { 51 | expect(_button.hasClass('btn btn-primary')).to.be.true() 52 | }) 53 | 54 | it('Calls props.increment when clicked', () => { 55 | _spies.dispatch.should.have.not.been.called() 56 | 57 | _button.simulate('click') 58 | 59 | _spies.dispatch.should.have.been.called() 60 | _spies.increment.should.have.been.called() 61 | }) 62 | }) 63 | 64 | describe('Double Async Button', () => { 65 | let _button 66 | 67 | beforeEach(() => { 68 | _button = _wrapper.find('button').filterWhere(a => a.text() === 'Double (Async)') 69 | }) 70 | 71 | it('exists', () => { 72 | expect(_button).to.exist() 73 | }) 74 | 75 | it('is a secondary button', () => { 76 | expect(_button.hasClass('btn btn-secondary')).to.be.true() 77 | }) 78 | 79 | it('Calls props.doubleAsync when clicked', () => { 80 | _spies.dispatch.should.have.not.been.called() 81 | 82 | _button.simulate('click') 83 | 84 | _spies.dispatch.should.have.been.called() 85 | _spies.doubleAsync.should.have.been.called() 86 | }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /tests/routes/Counter/index.spec.js: -------------------------------------------------------------------------------- 1 | import CounterRoute from 'routes/Counter' 2 | 3 | describe('(Route) Counter', () => { 4 | it('returns a route configuration object', () => { 5 | expect(typeof CounterRoute({})).to.equal('object') 6 | }) 7 | 8 | it('has a path \'counter\'', () => { 9 | expect(CounterRoute({}).path).to.equal('counter') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /tests/routes/Counter/modules/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | COUNTER_INCREMENT, 3 | increment, 4 | doubleAsync, 5 | default as counterReducer 6 | } from 'routes/Counter/modules/counter' 7 | 8 | describe('(Redux Module) Counter', () => { 9 | it('Should export a constant COUNTER_INCREMENT.', () => { 10 | expect(COUNTER_INCREMENT).to.equal('COUNTER_INCREMENT') 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(counterReducer).to.be.a('function') 16 | }) 17 | 18 | it('Should initialize with a state of 0 (Number).', () => { 19 | expect(counterReducer(undefined, {})).to.equal(0) 20 | }) 21 | 22 | it('Should return the previous state if an action was not matched.', () => { 23 | let state = counterReducer(undefined, {}) 24 | expect(state).to.equal(0) 25 | state = counterReducer(state, { type: '@@@@@@@' }) 26 | expect(state).to.equal(0) 27 | state = counterReducer(state, increment(5)) 28 | expect(state).to.equal(5) 29 | state = counterReducer(state, { type: '@@@@@@@' }) 30 | expect(state).to.equal(5) 31 | }) 32 | }) 33 | 34 | describe('(Action Creator) increment', () => { 35 | it('Should be exported as a function.', () => { 36 | expect(increment).to.be.a('function') 37 | }) 38 | 39 | it('Should return an action with type "COUNTER_INCREMENT".', () => { 40 | expect(increment()).to.have.property('type', COUNTER_INCREMENT) 41 | }) 42 | 43 | it('Should assign the first argument to the "payload" property.', () => { 44 | expect(increment(5)).to.have.property('payload', 5) 45 | }) 46 | 47 | it('Should default the "payload" property to 1 if not provided.', () => { 48 | expect(increment()).to.have.property('payload', 1) 49 | }) 50 | }) 51 | 52 | describe('(Action Creator) doubleAsync', () => { 53 | let _globalState 54 | let _dispatchSpy 55 | let _getStateSpy 56 | 57 | beforeEach(() => { 58 | _globalState = { 59 | counter : counterReducer(undefined, {}) 60 | } 61 | _dispatchSpy = sinon.spy((action) => { 62 | _globalState = { 63 | ..._globalState, 64 | counter : counterReducer(_globalState.counter, action) 65 | } 66 | }) 67 | _getStateSpy = sinon.spy(() => { 68 | return _globalState 69 | }) 70 | }) 71 | 72 | it('Should be exported as a function.', () => { 73 | expect(doubleAsync).to.be.a('function') 74 | }) 75 | 76 | it('Should return a function (is a thunk).', () => { 77 | expect(doubleAsync()).to.be.a('function') 78 | }) 79 | 80 | it('Should return a promise from that thunk that gets fulfilled.', () => { 81 | return doubleAsync()(_dispatchSpy, _getStateSpy).should.eventually.be.fulfilled 82 | }) 83 | 84 | it('Should call dispatch and getState exactly once.', () => { 85 | return doubleAsync()(_dispatchSpy, _getStateSpy) 86 | .then(() => { 87 | _dispatchSpy.should.have.been.calledOnce() 88 | _getStateSpy.should.have.been.calledOnce() 89 | }) 90 | }) 91 | 92 | it('Should produce a state that is double the previous state.', () => { 93 | _globalState = { counter: 2 } 94 | 95 | return doubleAsync()(_dispatchSpy, _getStateSpy) 96 | .then(() => { 97 | _dispatchSpy.should.have.been.calledOnce() 98 | _getStateSpy.should.have.been.calledOnce() 99 | expect(_globalState.counter).to.equal(4) 100 | return doubleAsync()(_dispatchSpy, _getStateSpy) 101 | }) 102 | .then(() => { 103 | _dispatchSpy.should.have.been.calledTwice() 104 | _getStateSpy.should.have.been.calledTwice() 105 | expect(_globalState.counter).to.equal(8) 106 | }) 107 | }) 108 | }) 109 | 110 | // NOTE: if you have a more complex state, you will probably want to verify 111 | // that you did not mutate the state. In this case our state is just a number 112 | // (which cannot be mutated). 113 | describe('(Action Handler) COUNTER_INCREMENT', () => { 114 | it('Should increment the state by the action payload\'s "value" property.', () => { 115 | let state = counterReducer(undefined, {}) 116 | expect(state).to.equal(0) 117 | state = counterReducer(state, increment(1)) 118 | expect(state).to.equal(1) 119 | state = counterReducer(state, increment(2)) 120 | expect(state).to.equal(3) 121 | state = counterReducer(state, increment(-3)) 122 | expect(state).to.equal(0) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /tests/routes/Home/components/HomeView.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { HomeView } from 'routes/Home/components/HomeView' 3 | import { render } from 'enzyme' 4 | 5 | describe('(View) Home', () => { 6 | let _component 7 | 8 | beforeEach(() => { 9 | _component = render() 10 | }) 11 | 12 | it('Renders a welcome message', () => { 13 | const welcome = _component.find('h4') 14 | expect(welcome).to.exist() 15 | expect(welcome.text()).to.match(/Welcome!/) 16 | }) 17 | 18 | it('Renders an awesome duck image', () => { 19 | const duck = _component.find('img') 20 | expect(duck).to.exist() 21 | expect(duck.attr('alt')).to.match(/This is a duck, because Redux!/) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/routes/Home/index.spec.js: -------------------------------------------------------------------------------- 1 | import HomeRoute from 'routes/Home' 2 | 3 | describe('(Route) Home', () => { 4 | let _component 5 | 6 | beforeEach(() => { 7 | _component = HomeRoute.component() 8 | }) 9 | 10 | it('Should return a route configuration object', () => { 11 | expect(typeof HomeRoute).to.equal('object') 12 | }) 13 | 14 | it('Should define a route component', () => { 15 | expect(_component.type).to.equal('div') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/store/createStore.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | default as createStore 3 | } from 'store/createStore' 4 | 5 | describe('(Store) createStore', () => { 6 | let store 7 | 8 | before(() => { 9 | store = createStore() 10 | }) 11 | 12 | it('should have an empty asyncReducers object', () => { 13 | expect(store.asyncReducers).to.be.an('object') 14 | expect(store.asyncReducers).to.be.empty() 15 | }) 16 | 17 | describe('(Location)', () => { 18 | it('store should be initialized with Location state', () => { 19 | const location = { 20 | pathname : '/echo' 21 | } 22 | store.dispatch({ 23 | type : 'LOCATION_CHANGE', 24 | payload : location 25 | }) 26 | expect(store.getState().location).to.deep.equal(location) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/store/location.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOCATION_CHANGE, 3 | locationChange, 4 | updateLocation, 5 | default as locationReducer 6 | } from 'store/location' 7 | 8 | describe('(Internal Module) Location', () => { 9 | it('Should export a constant LOCATION_CHANGE.', () => { 10 | expect(LOCATION_CHANGE).to.equal('LOCATION_CHANGE') 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(locationReducer).to.be.a('function') 16 | }) 17 | 18 | it('Should initialize with a location object.', () => { 19 | expect(locationReducer(undefined, {})).to.be.an('object') 20 | expect(locationReducer(undefined, {})).to.have.property('pathname') 21 | }) 22 | 23 | it('Should return the previous state if an action was not matched.', () => { 24 | let state = locationReducer(undefined, {}) 25 | expect(state).to.be.an('object') 26 | expect(state).to.have.property('pathname') 27 | expect(state).to.have.property('pathname', '/context.html') 28 | state = locationReducer(state, { type: '@@@@@@@' }) 29 | expect(state).to.have.property('pathname', '/context.html') 30 | 31 | const locationState = { pathname: '/yup' } 32 | state = locationReducer(state, locationChange(locationState)) 33 | expect(state).to.equal(locationState) 34 | expect(state).to.have.property('pathname', '/yup') 35 | state = locationReducer(state, { type: '@@@@@@@' }) 36 | expect(state).to.equal(locationState) 37 | expect(state).to.have.property('pathname', '/yup') 38 | }) 39 | }) 40 | 41 | describe('(Action Creator) locationChange', () => { 42 | it('Should be exported as a function.', () => { 43 | expect(locationChange).to.be.a('function') 44 | }) 45 | 46 | it('Should return an action with type "LOCATION_CHANGE".', () => { 47 | expect(locationChange()).to.have.property('type', LOCATION_CHANGE) 48 | }) 49 | 50 | it('Should assign the first argument to the "payload" property.', () => { 51 | const locationState = { pathname: '/yup' } 52 | expect(locationChange(locationState)).to.have.property('payload', locationState) 53 | }) 54 | 55 | it('Should default the "payload" property to "/" if not provided.', () => { 56 | expect(locationChange()).to.have.property('payload', '/') 57 | }) 58 | }) 59 | 60 | describe('(Specialized Action Creator) updateLocation', () => { 61 | let _globalState 62 | let _dispatchSpy 63 | 64 | beforeEach(() => { 65 | _globalState = { 66 | location : locationReducer(undefined, {}) 67 | } 68 | _dispatchSpy = sinon.spy((action) => { 69 | _globalState = { 70 | ..._globalState, 71 | location : locationReducer(_globalState.location, action) 72 | } 73 | }) 74 | }) 75 | 76 | it('Should be exported as a function.', () => { 77 | expect(updateLocation).to.be.a('function') 78 | }) 79 | 80 | it('Should return a function (is a thunk).', () => { 81 | expect(updateLocation({ dispatch: _dispatchSpy })).to.be.a('function') 82 | }) 83 | 84 | it('Should call dispatch exactly once.', () => { 85 | updateLocation({ dispatch: _dispatchSpy })('/') 86 | expect(_dispatchSpy.should.have.been.calledOnce) 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /tests/test-bundler.js: -------------------------------------------------------------------------------- 1 | import 'normalize.js' 2 | import chai from 'chai' 3 | import sinon from 'sinon' 4 | import dirtyChai from 'dirty-chai' 5 | import chaiAsPromised from 'chai-as-promised' 6 | import sinonChai from 'sinon-chai' 7 | import chaiEnzyme from 'chai-enzyme' 8 | 9 | // Mocha / Chai 10 | // ------------------------------------ 11 | mocha.setup({ ui: 'bdd' }) 12 | chai.should() 13 | 14 | global.chai = chai 15 | global.expect = chai.expect 16 | global.sinon = sinon 17 | 18 | // Chai Plugins 19 | // ------------------------------------ 20 | chai.use(chaiEnzyme()) 21 | chai.use(dirtyChai) 22 | chai.use(chaiAsPromised) 23 | chai.use(sinonChai) 24 | 25 | // Test Importer 26 | // ------------------------------------ 27 | // We use a Webpack global here as it is replaced with a string during compile. 28 | // Using a regular JS variable is not statically analyzable so webpack will throw warnings. 29 | const testsContext = require.context('./', true, /\.(spec|test)\.(js|ts|tsx)$/) 30 | 31 | // When a test file changes, only rerun that spec file. If something outside of a 32 | // test file changed, rerun all tests. 33 | // https://www.npmjs.com/package/karma-webpack-with-fast-source-maps 34 | const __karmaWebpackManifest__ = [] 35 | const allTests = testsContext.keys() 36 | const changedTests = allTests.filter(path => { 37 | return __karmaWebpackManifest__.indexOf(path) !== -1 38 | }) 39 | 40 | ;(changedTests.length ? changedTests : allTests).forEach(testsContext) 41 | --------------------------------------------------------------------------------