├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── backend ├── main.py ├── requirements.txt └── string_parser.py ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── client ├── index.html └── static │ ├── bootstrap.min.css │ ├── css │ ├── app.1ea883ee04aca0021ec1707aac027547.css │ └── app.1ea883ee04aca0021ec1707aac027547.css.map │ ├── favicon.ico │ ├── js │ ├── app.c64710128364b0205755.js │ ├── app.c64710128364b0205755.js.map │ ├── manifest.2ae2e69a05c33dfc65f8.js │ ├── manifest.2ae2e69a05c33dfc65f8.js.map │ ├── vendor.82d3d55112940f3d2ab7.js │ └── vendor.82d3d55112940f3d2ab7.js.map │ ├── logo.png │ ├── manifest.json │ └── site.css ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── install-orac-control-web.sh ├── orac-control-web.service ├── package-lock.json ├── package.json ├── screenshot.png ├── src ├── App.vue ├── components │ ├── Menu.vue │ ├── Navigation.vue │ ├── OracParameterControl.vue │ └── PieControl.vue ├── main.js └── store │ └── index.js └── static ├── .gitkeep ├── bootstrap.min.css ├── favicon.ico ├── logo.png ├── manifest.json └── site.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | __pycache__/ 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # orac-control-web 2 | 3 | Web client for the ORAC [synth](https://github.com/TheTechnobear/Orac) to be run on the Raspberry PI. Installation tested and designed to work out of the box on the [Patchbox OS](https://blokas.io/patchbox-os/). Web client also includes UDP OSC <-> Socket.IO bridge and simple static file server. It runs on the rPi, next to the ORAC, so all you need is a web browser essentially. Application is designed to be mobile-friendly. 4 | 5 | ![screenshot](https://raw.githubusercontent.com/dsedleckas/orac-control-web/master/screenshot.png) 6 | 7 | ## Install instructions 8 | Currently tested on [**Patchbox OS**](https://blokas.io/patchbox-os/). Application depends on `git`, `python3` (3.5, should work with 3.6, not sure about 3.7) and `pip3`. Install script will check if `git` is installed, but you need to manually verify that `python3` and `pip3` are available (`pip3 --version` and `python3 --version`). Install the missing dependencies through your package manager, e.g. `sudo apt-get install python3-pip`. 9 | 10 | SSH to your Pi and run: 11 | ```sh 12 | curl https://raw.githubusercontent.com/dsedleckas/orac-control-web/master/install-orac-control-web.sh | sh 13 | ``` 14 | Open `http://:8080` 15 | If you're stuck on loading screen, make sure ORAC module is activated and `mec.service` is running. If not, run `patchbox module config`. Any suggestions/comments/bug reports are welcome here, or on the Blokas [community board](https://community.blokas.io/t/web-client-for-orac-2-0/1186). 16 | 17 | ### Save as an app on your phone 18 | **If your Pi always has the same Ip address** (e.g. you're connecting to Pi's WiFi network and accessing `http://172.24.1.1:8080`), you could save the page as an app to your home screen. For example, on Chrome, once opened, click tripple dots and Add to Home screen. Launching application this way will open it in landscape mode and fullscreen instantly. 19 | 20 | ## Menu navigation 21 | Interface should be familiar to any ORAC user before - page/module navigation at the top and parameter controlls below. One change that was made involves menu navigation - _Activate_ button was dropped - instead just tap/click on the wanted item. To scroll the menu (i.e. see more items in the list), you still have to use up/down buttons. 22 | 23 | ## Development notes 24 | 25 | ### Web client 26 | ``` bash 27 | # install client dependencies 28 | npm install 29 | 30 | # serve with hot reload at localhost:8080 31 | npm run dev 32 | 33 | # build for production with minification 34 | npm run build 35 | # For production use copy `./dist` contents to `./client` 36 | ``` 37 | 38 | ### Server OSC <-> Socket.IO 39 | ```bash 40 | cd ./backend 41 | pip3 install -r requirements.txt 42 | python3 main.py 43 | ``` 44 | -------------------------------------------------------------------------------- /backend/main.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | import socketio 3 | import asyncio 4 | 5 | from pythonosc.udp_client import SimpleUDPClient 6 | from pythonosc.osc_server import AsyncIOOSCUDPServer 7 | from pythonosc.dispatcher import Dispatcher 8 | import string_parser 9 | 10 | # Monkey-patch for osc empty string support 11 | string_parser.support_empty_strings() 12 | 13 | ORAC_IP = '127.0.0.1' 14 | ORAC_PORT = 6100 15 | ORAC_LISTEN_PORT = 6009 16 | SOCKET_LISTEN_IP = '0.0.0.0' 17 | SOCKET_PORT = 8080 18 | 19 | # 20 | # Async event loop 21 | # 22 | 23 | loop = asyncio.get_event_loop() 24 | 25 | # 26 | # ORAC UDP client 27 | # 28 | 29 | oracClient = SimpleUDPClient(ORAC_IP, ORAC_PORT) # Create client 30 | 31 | # 32 | # ORAC UDP listener 33 | # 34 | 35 | 36 | def oracMessageHandler(address, *args): 37 | asyncio.ensure_future(sio.emit(address[1:], args)) 38 | 39 | dispatcher = Dispatcher() 40 | dispatcher.set_default_handler(oracMessageHandler) 41 | 42 | oracServerTransport = None 43 | 44 | async def runUdpServer(): 45 | print('Starting OSC UDP server') 46 | server = AsyncIOOSCUDPServer( 47 | (SOCKET_LISTEN_IP, ORAC_LISTEN_PORT), dispatcher, loop) 48 | global oracServerTransport 49 | oracServerTransport, protocol = await server.create_serve_endpoint() 50 | print('OSC UDP server started') 51 | 52 | 53 | async def stopUdpServer(): 54 | print('Stopping OSC UDP server') 55 | oracServerTransport.close() # Clean up serve endpoint 56 | print('OSC UDP server stopped') 57 | 58 | # 59 | # Socket IO server 60 | # 61 | 62 | sio = socketio.AsyncServer(async_mode='aiohttp') 63 | app = web.Application() 64 | sio.attach(app) 65 | 66 | 67 | async def root_handler(request): 68 | return web.HTTPFound('/index.html') 69 | 70 | app.router.add_route('GET', '/', root_handler) 71 | app.router.add_static('/', path='../client/') 72 | 73 | 74 | @sio.event 75 | def connect(sid, environ): 76 | print('connect ', sid) 77 | 78 | 79 | @sio.on('OracConnect') 80 | async def OracConnect(sid): 81 | print('OracConnect!') 82 | oracClient.send_message('/Connect', ORAC_LISTEN_PORT) 83 | await sio.emit('ORAC_CONNECTED', True) 84 | 85 | 86 | @sio.on('/NavNext') 87 | async def NavNext(sid, data): 88 | oracClient.send_message('/NavNext', 1) 89 | 90 | 91 | @sio.on('/NavPrev') 92 | async def NavPrev(sid, data): 93 | oracClient.send_message('/NavPrev', 1) 94 | 95 | 96 | @sio.on('/NavActivate') 97 | async def NavActivate(sid, data): 98 | oracClient.send_message('/NavActivate', 1) 99 | 100 | 101 | @sio.on('/PagePrev') 102 | async def PagePrev(sid, data): 103 | oracClient.send_message('/PagePrev', 1) 104 | 105 | 106 | @sio.on('/PageNext') 107 | async def PageNext(sid, data): 108 | oracClient.send_message('/PageNext', 1) 109 | 110 | 111 | @sio.on('/ModuleNext') 112 | async def ModuleNext(sid, data): 113 | oracClient.send_message('/ModuleNext', 1) 114 | 115 | 116 | @sio.on('/ModulePrev') 117 | async def ModulePrev(sid, data): 118 | oracClient.send_message('/ModulePrev', 1) 119 | 120 | 121 | @sio.on('/P1Ctrl') 122 | async def ModuleNext(sid, data): 123 | oracClient.send_message('/P1Ctrl', data) 124 | 125 | 126 | @sio.on('/P2Ctrl') 127 | async def P2Ctrl(sid, data): 128 | oracClient.send_message('/P2Ctrl', data) 129 | 130 | 131 | @sio.on('/P3Ctrl') 132 | async def P3Ctrl(sid, data): 133 | oracClient.send_message('/P3Ctrl', data) 134 | 135 | 136 | @sio.on('/P4Ctrl') 137 | async def P4Ctrl(sid, data): 138 | oracClient.send_message('/P4Ctrl', data) 139 | 140 | 141 | @sio.on('/P5Ctrl') 142 | async def P5Ctrl(sid, data): 143 | oracClient.send_message('/P5Ctrl', data) 144 | 145 | 146 | @sio.on('/P6Ctrl') 147 | async def P6Ctrl(sid, data): 148 | oracClient.send_message('/P6Ctrl', data) 149 | 150 | 151 | @sio.on('/P7Ctrl') 152 | async def P7Ctrl(sid, data): 153 | oracClient.send_message('/P7Ctrl', data) 154 | 155 | 156 | @sio.on('/P8Ctrl') 157 | async def P8Ctrl(sid, data): 158 | oracClient.send_message('/P8Ctrl', data) 159 | 160 | 161 | @sio.event 162 | def disconnect(sid): 163 | print('disconnect ', sid) 164 | 165 | # We kick off our server 166 | 167 | 168 | runner = None 169 | 170 | 171 | async def runSocketServer(): 172 | print('Starting Socket IO server') 173 | global runner 174 | runner = web.AppRunner(app) 175 | await runner.setup() 176 | site = web.TCPSite(runner, '0.0.0.0', SOCKET_PORT) 177 | await site.start() 178 | print('Socket IO server running') 179 | 180 | 181 | async def stopSocketServer(): 182 | print('Stopping Socket IO server') 183 | await runner.cleanup() 184 | print('Socket IO server stopped') 185 | 186 | 187 | if __name__ == '__main__': 188 | try: 189 | loop.run_until_complete(runSocketServer()) 190 | loop.run_until_complete(runUdpServer()) 191 | loop.run_forever() 192 | except KeyboardInterrupt: 193 | pass 194 | print('Stopping...') 195 | loop.run_until_complete(stopSocketServer()) 196 | loop.run_until_complete(stopUdpServer()) 197 | print('All complete!') 198 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | python-socketio 2 | asyncio 3 | python-osc 4 | aiohttp -------------------------------------------------------------------------------- /backend/string_parser.py: -------------------------------------------------------------------------------- 1 | from pythonosc.parsing import osc_types 2 | 3 | original_get_string = osc_types.get_string 4 | def get_string(dgram: bytes, start_index: int): 5 | offset = 0 6 | while dgram[start_index + offset] != 0: 7 | offset += 1 8 | if offset == 0: 9 | return '', 0 10 | return original_get_string(dgram, start_index) 11 | 12 | def support_empty_strings(): 13 | osc_types.get_string = get_string -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsedleckas/orac-control-web/b42f20da27075c6265dcdc5bde42fbe822adf72f/build/logo.png -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | }, 78 | // this will apply to both plain `.scss` files 79 | // AND `\r\n\n\n\n// WEBPACK FOOTER //\n// src/components/Navigation.vue","var normalizeComponent = require(\"!../../node_modules/vue-loader/lib/component-normalizer\")\n/* script */\nexport * from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./Menu.vue\"\nimport __vue_script__ from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./Menu.vue\"\n/* template */\nimport __vue_template__ from \"!!../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-5a0041dd\\\",\\\"hasScoped\\\":false,\\\"transformToRequire\\\":{\\\"video\\\":[\\\"src\\\",\\\"poster\\\"],\\\"source\\\":\\\"src\\\",\\\"img\\\":\\\"src\\\",\\\"image\\\":\\\"xlink:href\\\"},\\\"buble\\\":{\\\"transforms\\\":{}}}!../../node_modules/vue-loader/lib/selector?type=template&index=0!./Menu.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = null\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_template__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/Menu.vue\n// module id = null\n// module chunks = ","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"navigation\"}},[_c('div',{staticClass:\"d-flex justify-content-between w-100\"},[_c('div',{staticClass:\"p-1 btn-group btn-group-sm w-100\",attrs:{\"role\":\"group\"}},[_c('button',{staticClass:\"btn btn-arrow btn-outline-secondary\",attrs:{\"type\":\"button\"},on:{\"click\":function($event){return _vm.prevModule()}}},[_vm._v(\"◄\")]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-outline-secondary disabled\",staticStyle:{\"opacity\":\"1\"},attrs:{\"type\":\"button\"}},[_c('small',[_vm._v(_vm._s(_vm.module))])]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-arrow btn-outline-secondary\",attrs:{\"type\":\"button\"},on:{\"click\":function($event){return _vm.nextModule()}}},[_vm._v(\"►\")])]),_vm._v(\" \"),_c('div',{staticClass:\"p-1 btn-group btn-group-sm w-100\",attrs:{\"role\":\"group\"}},[_c('button',{staticClass:\"btn btn-arrow btn-outline-secondary\",class:{ 'disabled': !_vm.page },attrs:{\"type\":\"button\"},on:{\"click\":function($event){return _vm.prevPage()}}},[_vm._v(\"◄\")]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-outline-secondary disabled\",staticStyle:{\"opacity\":\"1\"},attrs:{\"type\":\"button\"}},[_c('small',[_vm._v(_vm._s(_vm.page || '-'))])]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-arrow btn-outline-secondary\",class:{ 'disabled': !_vm.page },attrs:{\"type\":\"button\"},on:{\"click\":function($event){return _vm.nextPage()}}},[_vm._v(\"►\")])]),_vm._v(\" \"),_c('div',{staticClass:\"p-1\"},[_c('button',{staticClass:\"btn btn-outline-secondary\",staticStyle:{\"height\":\"100%\"},attrs:{\"type\":\"button\"},on:{\"click\":function($event){_vm.showModal = true}}},[_vm._v(\"☰\")])])]),_vm._v(\" \"),(_vm.showModal)?_c('div',[_c('transition',{attrs:{\"name\":\"modal\"}},[_c('div',{staticClass:\"modal-mask\",on:{\"click\":function($event){_vm.showModal = false}}},[_c('div',{staticClass:\"modal-wrapper\"},[_c('div',{staticClass:\"modal-dialog\",attrs:{\"role\":\"document\"}},[_c('div',{staticClass:\"modal-content\",staticStyle:{\"border\":\"none\"}},[_c('Menu')],1)])])])])],1):_vm._e()])}\nvar staticRenderFns = []\nvar esExports = { render: render, staticRenderFns: staticRenderFns }\nexport default esExports\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./node_modules/vue-loader/lib/template-compiler?{\"id\":\"data-v-7723e048\",\"hasScoped\":true,\"transformToRequire\":{\"video\":[\"src\",\"poster\"],\"source\":\"src\",\"img\":\"src\",\"image\":\"xlink:href\"},\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/components/Navigation.vue\n// module id = null\n// module chunks = ","function injectStyle (ssrContext) {\n require(\"!!../../node_modules/extract-text-webpack-plugin/dist/loader.js?{\\\"omit\\\":1,\\\"remove\\\":true}!vue-style-loader!css-loader?{\\\"sourceMap\\\":true}!../../node_modules/vue-loader/lib/style-compiler/index?{\\\"vue\\\":true,\\\"id\\\":\\\"data-v-7723e048\\\",\\\"scoped\\\":true,\\\"hasInlineConfig\\\":false}!../../node_modules/vue-loader/lib/selector?type=styles&index=0!./Navigation.vue\")\n}\nvar normalizeComponent = require(\"!../../node_modules/vue-loader/lib/component-normalizer\")\n/* script */\nexport * from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./Navigation.vue\"\nimport __vue_script__ from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./Navigation.vue\"\n/* template */\nimport __vue_template__ from \"!!../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-7723e048\\\",\\\"hasScoped\\\":true,\\\"transformToRequire\\\":{\\\"video\\\":[\\\"src\\\",\\\"poster\\\"],\\\"source\\\":\\\"src\\\",\\\"img\\\":\\\"src\\\",\\\"image\\\":\\\"xlink:href\\\"},\\\"buble\\\":{\\\"transforms\\\":{}}}!../../node_modules/vue-loader/lib/selector?type=template&index=0!./Navigation.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = \"data-v-7723e048\"\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_template__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/Navigation.vue\n// module id = null\n// module chunks = ","\r\n\r\n\r\n\r\n\r\n\r\n\n\n\n// WEBPACK FOOTER //\n// src/components/PieControl.vue","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"noselect\"},[_c('svg',{ref:\"svg\",attrs:{\"viewBox\":\"0 0 160 130\"}},[_c('g',{attrs:{\"transform\":_vm.translate}},[_c('path',{attrs:{\"d\":_vm.drawArc(0),\"fill\":\"#ff0000\",\"stroke\":\"black\"}}),_vm._v(\" \"),_c('path',{attrs:{\"d\":_vm.drawArc(1),\"fill\":\"#6c757d\",\"stroke\":\"black\"}}),_vm._v(\" \"),_c('text',{attrs:{\"text-anchor\":\"middle\"}},[_vm._v(_vm._s((this.value * 100).toFixed(0)))])])])])}\nvar staticRenderFns = []\nvar esExports = { render: render, staticRenderFns: staticRenderFns }\nexport default esExports\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./node_modules/vue-loader/lib/template-compiler?{\"id\":\"data-v-39423519\",\"hasScoped\":true,\"transformToRequire\":{\"video\":[\"src\",\"poster\"],\"source\":\"src\",\"img\":\"src\",\"image\":\"xlink:href\"},\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/components/PieControl.vue\n// module id = null\n// module chunks = ","function injectStyle (ssrContext) {\n require(\"!!../../node_modules/extract-text-webpack-plugin/dist/loader.js?{\\\"omit\\\":1,\\\"remove\\\":true}!vue-style-loader!css-loader?{\\\"sourceMap\\\":true}!../../node_modules/vue-loader/lib/style-compiler/index?{\\\"vue\\\":true,\\\"id\\\":\\\"data-v-39423519\\\",\\\"scoped\\\":true,\\\"hasInlineConfig\\\":false}!../../node_modules/vue-loader/lib/selector?type=styles&index=0!./PieControl.vue\")\n}\nvar normalizeComponent = require(\"!../../node_modules/vue-loader/lib/component-normalizer\")\n/* script */\nexport * from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./PieControl.vue\"\nimport __vue_script__ from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./PieControl.vue\"\n/* template */\nimport __vue_template__ from \"!!../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-39423519\\\",\\\"hasScoped\\\":true,\\\"transformToRequire\\\":{\\\"video\\\":[\\\"src\\\",\\\"poster\\\"],\\\"source\\\":\\\"src\\\",\\\"img\\\":\\\"src\\\",\\\"image\\\":\\\"xlink:href\\\"},\\\"buble\\\":{\\\"transforms\\\":{}}}!../../node_modules/vue-loader/lib/selector?type=template&index=0!./PieControl.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = injectStyle\n/* scopeId */\nvar __vue_scopeId__ = \"data-v-39423519\"\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_template__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/PieControl.vue\n// module id = null\n// module chunks = ","\r\n\r\n\r\n\n\n\n// WEBPACK FOOTER //\n// src/components/OracParameterControl.vue","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"param-block\"},[_c('div',{staticClass:\"d-flex justify-content-between\"},[_c('div',{staticClass:\"param-text small font-weight-bold\"},[_vm._v(_vm._s(_vm.desc))]),_vm._v(\" \"),_c('div',{staticClass:\"param-text small font-weight-bold\"},[_vm._v(_vm._s(_vm.value))])]),_vm._v(\" \"),(_vm.desc)?_c('PieControl',{attrs:{\"prefix\":_vm.prefix}}):_vm._e()],1)}\nvar staticRenderFns = []\nvar esExports = { render: render, staticRenderFns: staticRenderFns }\nexport default esExports\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./node_modules/vue-loader/lib/template-compiler?{\"id\":\"data-v-d41bfeb8\",\"hasScoped\":false,\"transformToRequire\":{\"video\":[\"src\",\"poster\"],\"source\":\"src\",\"img\":\"src\",\"image\":\"xlink:href\"},\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/components/OracParameterControl.vue\n// module id = null\n// module chunks = ","\r\n\r\n\r\n\r\n\n\n\n// WEBPACK FOOTER //\n// src/App.vue","var normalizeComponent = require(\"!../../node_modules/vue-loader/lib/component-normalizer\")\n/* script */\nexport * from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./OracParameterControl.vue\"\nimport __vue_script__ from \"!!babel-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./OracParameterControl.vue\"\n/* template */\nimport __vue_template__ from \"!!../../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-d41bfeb8\\\",\\\"hasScoped\\\":false,\\\"transformToRequire\\\":{\\\"video\\\":[\\\"src\\\",\\\"poster\\\"],\\\"source\\\":\\\"src\\\",\\\"img\\\":\\\"src\\\",\\\"image\\\":\\\"xlink:href\\\"},\\\"buble\\\":{\\\"transforms\\\":{}}}!../../node_modules/vue-loader/lib/selector?type=template&index=0!./OracParameterControl.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = null\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_template__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/OracParameterControl.vue\n// module id = null\n// module chunks = ","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"app\"}},[(_vm.oracConnected)?_c('div',{attrs:{\"id\":\"connected\"}},[_c('Navigation'),_vm._v(\" \"),_c('div',{staticClass:\"params-container\"},_vm._l((8),function(no){return _c('OracParameterControl',{key:no,attrs:{\"prefix\":'P' + no}})}),1)],1):_c('div',{staticClass:\"container-centered text-center\",attrs:{\"id\":\"connecting\"}},[_c('div',{staticStyle:{\"min-height\":\"180px\"}},[_c('img',{staticClass:\"logo\",attrs:{\"src\":'/static/logo.png'}})]),_vm._v(\" \"),(_vm.wsConnected)?_c('strong',[_vm._v(\"Looking for MEC Service...\")]):_c('strong',[_vm._v(\"Looking for OSC Service...\")])])])}\nvar staticRenderFns = []\nvar esExports = { render: render, staticRenderFns: staticRenderFns }\nexport default esExports\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./node_modules/vue-loader/lib/template-compiler?{\"id\":\"data-v-17648587\",\"hasScoped\":false,\"transformToRequire\":{\"video\":[\"src\",\"poster\"],\"source\":\"src\",\"img\":\"src\",\"image\":\"xlink:href\"},\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/App.vue\n// module id = null\n// module chunks = ","var normalizeComponent = require(\"!../node_modules/vue-loader/lib/component-normalizer\")\n/* script */\nexport * from \"!!babel-loader!../node_modules/vue-loader/lib/selector?type=script&index=0!./App.vue\"\nimport __vue_script__ from \"!!babel-loader!../node_modules/vue-loader/lib/selector?type=script&index=0!./App.vue\"\n/* template */\nimport __vue_template__ from \"!!../node_modules/vue-loader/lib/template-compiler/index?{\\\"id\\\":\\\"data-v-17648587\\\",\\\"hasScoped\\\":false,\\\"transformToRequire\\\":{\\\"video\\\":[\\\"src\\\",\\\"poster\\\"],\\\"source\\\":\\\"src\\\",\\\"img\\\":\\\"src\\\",\\\"image\\\":\\\"xlink:href\\\"},\\\"buble\\\":{\\\"transforms\\\":{}}}!../node_modules/vue-loader/lib/selector?type=template&index=0!./App.vue\"\n/* template functional */\nvar __vue_template_functional__ = false\n/* styles */\nvar __vue_styles__ = null\n/* scopeId */\nvar __vue_scopeId__ = null\n/* moduleIdentifier (server only) */\nvar __vue_module_identifier__ = null\nvar Component = normalizeComponent(\n __vue_script__,\n __vue_template__,\n __vue_template_functional__,\n __vue_styles__,\n __vue_scopeId__,\n __vue_module_identifier__\n)\n\nexport default Component.exports\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/App.vue\n// module id = null\n// module chunks = ","import Vue from 'vue'\r\nimport Vuex from 'vuex'\r\nimport 'es6-promise/auto'\r\n\r\nVue.use(Vuex)\r\n\r\nconst store = new Vuex.Store({\r\n state: {\r\n wsConnected: false,\r\n oracConnected: false,\r\n P1Desc: '',\r\n P1Value: '',\r\n P1Ctrl: 0,\r\n P2Desc: '',\r\n P2Value: '',\r\n P2Ctrl: 0,\r\n P3Desc: '',\r\n P3Value: '',\r\n P3Ctrl: 0,\r\n P4Desc: '',\r\n P4Value: '',\r\n P4Ctrl: 0,\r\n P5Desc: '',\r\n P5Value: '',\r\n P5Ctrl: 0,\r\n P6Desc: '',\r\n P6Value: '',\r\n P6Ctrl: 0,\r\n P7Desc: '',\r\n P7Value: '',\r\n P7Ctrl: 0,\r\n P8Desc: '',\r\n P8Value: '',\r\n P8Ctrl: 0,\r\n T1: '',\r\n T2: '',\r\n T3: '',\r\n T4: '',\r\n T5: '',\r\n selectText: null,\r\n module: 'Ask Mark',\r\n page: 'Ask Mark'\r\n },\r\n mutations: {\r\n SET_WS_CONNECTED: (state, bool) => {\r\n state.wsConnected = bool\r\n },\r\n SET_ORAC_CONNECTED: (state, bool) => {\r\n state.oracConnected = bool\r\n },\r\n SET_FIELD: (state, nameFieldValueArray) => {\r\n state[nameFieldValueArray[0] + nameFieldValueArray[1]] = nameFieldValueArray[2]\r\n },\r\n SOCKET_text: (state, lineValueArray) => {\r\n switch (lineValueArray[0]) {\r\n case 1:\r\n state.T1 = lineValueArray[1]\r\n break\r\n case 2:\r\n state.T2 = lineValueArray[1]\r\n break\r\n case 3:\r\n state.T3 = lineValueArray[1]\r\n break\r\n case 4:\r\n state.T4 = lineValueArray[1]\r\n break\r\n case 5:\r\n state.T5 = lineValueArray[1]\r\n break\r\n }\r\n },\r\n SOCKET_selectText: (state, lineNumber) => {\r\n state.selectText = --lineNumber\r\n },\r\n SOCKET_page: (state, pageName) => {\r\n state.page = pageName\r\n },\r\n SOCKET_module: (state, moduleName) => {\r\n state.module = moduleName\r\n },\r\n SOCKET_clearText: (state) => {\r\n state.T1 = state.T2 = state.T3 = state.T4 = state.T5 = ''\r\n }\r\n },\r\n actions: {\r\n socket_connect (context) {\r\n setTimeout(() => {\r\n context.commit('SET_WS_CONNECTED', true)\r\n try {\r\n this._vm.$socket.emit('OracConnect')\r\n } catch (err) {\r\n this.a._vm.$socket.emit('OracConnect')\r\n }\r\n }, 500)\r\n },\r\n socket_ORAC_CONNECTED: (context) => {\r\n setTimeout(() => {\r\n context.commit('SET_ORAC_CONNECTED', true)\r\n }, 500)\r\n },\r\n socket_P1Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P1', 'Desc', desc])\r\n },\r\n socket_P1Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P1', 'Ctrl', ctrl])\r\n },\r\n socket_P1Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P1', 'Value', value])\r\n },\r\n socket_P2Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P2', 'Desc', desc])\r\n },\r\n socket_P2Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P2', 'Ctrl', ctrl])\r\n },\r\n socket_P2Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P2', 'Value', value])\r\n },\r\n socket_P3Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P3', 'Desc', desc])\r\n },\r\n socket_P3Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P3', 'Ctrl', ctrl])\r\n },\r\n socket_P3Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P3', 'Value', value])\r\n },\r\n socket_P4Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P4', 'Desc', desc])\r\n },\r\n socket_P4Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P4', 'Ctrl', ctrl])\r\n },\r\n socket_P4Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P4', 'Value', value])\r\n },\r\n socket_P5Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P5', 'Desc', desc])\r\n },\r\n socket_P5Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P5', 'Ctrl', ctrl])\r\n },\r\n socket_P5Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P5', 'Value', value])\r\n },\r\n socket_P6Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P6', 'Desc', desc])\r\n },\r\n socket_P6Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P6', 'Ctrl', ctrl])\r\n },\r\n socket_P6Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P6', 'Value', value])\r\n },\r\n socket_P7Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P7', 'Desc', desc])\r\n },\r\n socket_P7Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P7', 'Ctrl', ctrl])\r\n },\r\n socket_P7Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P7', 'Value', value])\r\n },\r\n socket_P8Desc: (context, desc) => {\r\n context.commit('SET_FIELD', ['P8', 'Desc', desc])\r\n },\r\n socket_P8Ctrl: (context, ctrl) => {\r\n context.commit('SET_FIELD', ['P8', 'Ctrl', ctrl])\r\n },\r\n socket_P8Value: (context, value) => {\r\n context.commit('SET_FIELD', ['P8', 'Value', value])\r\n }\r\n },\r\n getters: {\r\n wsConnected: state => {\r\n return state.wsConnected\r\n },\r\n oracConnected: state => {\r\n return state.oracConnected\r\n },\r\n page: state => {\r\n return state.page\r\n },\r\n module: state => {\r\n return state.module\r\n },\r\n text: state => {\r\n return [state.T1, state.T2, state.T3, state.T4, state.T5]\r\n },\r\n selectText: state => {\r\n return state.selectText\r\n },\r\n getField: (state) => (param, field) => {\r\n return state[param + field]\r\n }\r\n }\r\n})\r\n\r\nexport default store\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/store/index.js","// The Vue build version to load with the `import` command\r\n// (runtime-only or standalone) has been set in webpack.base.conf with an alias.\r\nimport Vue from 'vue'\r\nimport App from './App'\r\nimport VueSocketIO from 'vue-socket.io'\r\nimport store from './store/index'\r\n\r\nVue.use(new VueSocketIO({\r\n debug: false,\r\n // config/dev.env.js\r\n connection: (process.env.WS_URL) ? process.env.WS_URL : 'http://' + window.location.host,\r\n vuex: {\r\n store,\r\n actionPrefix: 'socket_',\r\n mutationPrefix: 'SOCKET_'\r\n }\r\n}))\r\n\r\nVue.config.productionTip = true\r\n\r\n/* eslint-disable no-new */\r\nnew Vue({\r\n el: '#app',\r\n store,\r\n render: h => h(App)\r\n})\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/main.js"],"sourceRoot":""} -------------------------------------------------------------------------------- /client/static/js/manifest.2ae2e69a05c33dfc65f8.js: -------------------------------------------------------------------------------- 1 | !function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a 2 | 3 | 4 | 5 | 6 | Orac Client 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /install-orac-control-web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPO_NAME=orac-control-web 4 | REPO_URL=https://github.com/dsedleckas/$REPO_NAME.git 5 | APP_DIR=/usr/local/orac-control-web 6 | 7 | cd $HOME 8 | 9 | pwd 10 | 11 | if [ -z `which git` ]; then 12 | echo "Installing git..." 13 | sudo apt-get update 14 | sudo apt-get install -y git 15 | fi 16 | 17 | if [ ! -d "$REPO_NAME" ]; then 18 | echo Cloning repository from $REPO_URL 19 | git clone $REPO_URL 20 | cd $REPO_NAME 21 | else 22 | echo Updating $REPO_NAME repository with latest stuff 23 | cd $REPO_NAME 24 | git pull 25 | fi 26 | 27 | echo "Setup started..." 28 | pip3 install -r backend/requirements.txt 29 | sudo rm -rf $APP_DIR 30 | sudo mkdir $APP_DIR 31 | sudo mkdir $APP_DIR/client 32 | sudo mkdir $APP_DIR/backend 33 | sudo cp -R client/. $APP_DIR/client 34 | sudo cp -R backend/. $APP_DIR/backend 35 | sudo install -v -m 644 orac-control-web.service /usr/lib/systemd/system/ 36 | sudo systemctl daemon-reload 37 | sudo systemctl enable orac-control-web.service 38 | sudo systemctl start orac-control-web.service 39 | 40 | echo "Done! Thank you!" -------------------------------------------------------------------------------- /orac-control-web.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Orac Control Service 3 | After=mec.service 4 | Requires=mec.service 5 | 6 | [Service] 7 | User=patch 8 | Environment=HOME=/home/patch 9 | Restart=always 10 | WorkingDirectory=/usr/local/orac-control-web/backend 11 | ExecStart=/usr/bin/python3 ./main.py 12 | 13 | [Install] 14 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orac-control-web", 3 | "version": "1.0.0", 4 | "description": "Client for ORAC synth", 5 | "author": "dsedleckas ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "lint": "eslint --ext .js,.vue src", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "es6-promise": "^4.2.8", 14 | "vue": "^2.5.2", 15 | "vue-socket.io": "^3.0.7", 16 | "vuex": "^3.1.1" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.2", 20 | "babel-core": "^6.22.1", 21 | "babel-eslint": "^8.2.1", 22 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 23 | "babel-loader": "^7.1.1", 24 | "babel-plugin-syntax-jsx": "^6.18.0", 25 | "babel-plugin-transform-runtime": "^6.22.0", 26 | "babel-plugin-transform-vue-jsx": "^3.5.0", 27 | "babel-preset-env": "^1.3.2", 28 | "babel-preset-stage-2": "^6.22.0", 29 | "chalk": "^2.0.1", 30 | "copy-webpack-plugin": "^4.0.1", 31 | "css-loader": "^0.28.0", 32 | "d3": "^5.9.2", 33 | "eslint": "^4.15.0", 34 | "eslint-config-standard": "^10.2.1", 35 | "eslint-friendly-formatter": "^3.0.0", 36 | "eslint-loader": "^1.7.1", 37 | "eslint-plugin-import": "^2.7.0", 38 | "eslint-plugin-node": "^5.2.0", 39 | "eslint-plugin-promise": "^3.4.0", 40 | "eslint-plugin-standard": "^3.0.1", 41 | "eslint-plugin-vue": "^4.0.0", 42 | "extract-text-webpack-plugin": "^3.0.0", 43 | "file-loader": "^1.1.4", 44 | "friendly-errors-webpack-plugin": "^1.6.1", 45 | "html-webpack-plugin": "^2.30.1", 46 | "node-notifier": "^5.1.2", 47 | "node-sass": "^4.12.0", 48 | "optimize-css-assets-webpack-plugin": "^3.2.0", 49 | "ora": "^1.2.0", 50 | "portfinder": "^1.0.13", 51 | "postcss-import": "^11.0.0", 52 | "postcss-loader": "^2.0.8", 53 | "postcss-url": "^7.2.1", 54 | "rimraf": "^2.6.0", 55 | "sass-loader": "^7.1.0", 56 | "semver": "^5.3.0", 57 | "shelljs": "^0.7.6", 58 | "style-loader": "^0.23.1", 59 | "uglifyjs-webpack-plugin": "^1.1.1", 60 | "url-loader": "^0.5.8", 61 | "vue-loader": "^13.3.0", 62 | "vue-style-loader": "^3.0.1", 63 | "vue-template-compiler": "^2.5.2", 64 | "webpack": "^3.6.0", 65 | "webpack-bundle-analyzer": "^2.9.0", 66 | "webpack-dev-server": "^2.9.1", 67 | "webpack-merge": "^4.1.0" 68 | }, 69 | "engines": { 70 | "node": ">= 6.0.0", 71 | "npm": ">= 3.0.0" 72 | }, 73 | "browserslist": [ 74 | "> 1%", 75 | "last 2 versions", 76 | "not ie <= 8" 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsedleckas/orac-control-web/b42f20da27075c6265dcdc5bde42fbe822adf72f/screenshot.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 39 | -------------------------------------------------------------------------------- /src/components/Menu.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 58 | -------------------------------------------------------------------------------- /src/components/Navigation.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 83 | 84 | 106 | -------------------------------------------------------------------------------- /src/components/OracParameterControl.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | -------------------------------------------------------------------------------- /src/components/PieControl.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 130 | 131 | 132 | 144 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import VueSocketIO from 'vue-socket.io' 6 | import store from './store/index' 7 | 8 | Vue.use(new VueSocketIO({ 9 | debug: false, 10 | // config/dev.env.js 11 | connection: (process.env.WS_URL) ? process.env.WS_URL : 'http://' + window.location.host, 12 | vuex: { 13 | store, 14 | actionPrefix: 'socket_', 15 | mutationPrefix: 'SOCKET_' 16 | } 17 | })) 18 | 19 | Vue.config.productionTip = true 20 | 21 | /* eslint-disable no-new */ 22 | new Vue({ 23 | el: '#app', 24 | store, 25 | render: h => h(App) 26 | }) 27 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import 'es6-promise/auto' 4 | 5 | Vue.use(Vuex) 6 | 7 | const store = new Vuex.Store({ 8 | state: { 9 | wsConnected: false, 10 | oracConnected: false, 11 | P1Desc: '', 12 | P1Value: '', 13 | P1Ctrl: 0, 14 | P2Desc: '', 15 | P2Value: '', 16 | P2Ctrl: 0, 17 | P3Desc: '', 18 | P3Value: '', 19 | P3Ctrl: 0, 20 | P4Desc: '', 21 | P4Value: '', 22 | P4Ctrl: 0, 23 | P5Desc: '', 24 | P5Value: '', 25 | P5Ctrl: 0, 26 | P6Desc: '', 27 | P6Value: '', 28 | P6Ctrl: 0, 29 | P7Desc: '', 30 | P7Value: '', 31 | P7Ctrl: 0, 32 | P8Desc: '', 33 | P8Value: '', 34 | P8Ctrl: 0, 35 | T1: '', 36 | T2: '', 37 | T3: '', 38 | T4: '', 39 | T5: '', 40 | selectText: null, 41 | module: 'Ask Mark', 42 | page: 'Ask Mark' 43 | }, 44 | mutations: { 45 | SET_WS_CONNECTED: (state, bool) => { 46 | state.wsConnected = bool 47 | }, 48 | SET_ORAC_CONNECTED: (state, bool) => { 49 | state.oracConnected = bool 50 | }, 51 | SET_FIELD: (state, nameFieldValueArray) => { 52 | state[nameFieldValueArray[0] + nameFieldValueArray[1]] = nameFieldValueArray[2] 53 | }, 54 | SOCKET_text: (state, lineValueArray) => { 55 | switch (lineValueArray[0]) { 56 | case 1: 57 | state.T1 = lineValueArray[1] 58 | break 59 | case 2: 60 | state.T2 = lineValueArray[1] 61 | break 62 | case 3: 63 | state.T3 = lineValueArray[1] 64 | break 65 | case 4: 66 | state.T4 = lineValueArray[1] 67 | break 68 | case 5: 69 | state.T5 = lineValueArray[1] 70 | break 71 | } 72 | }, 73 | SOCKET_selectText: (state, lineNumber) => { 74 | state.selectText = --lineNumber 75 | }, 76 | SOCKET_page: (state, pageName) => { 77 | state.page = pageName 78 | }, 79 | SOCKET_module: (state, moduleName) => { 80 | state.module = moduleName 81 | }, 82 | SOCKET_clearText: (state) => { 83 | state.T1 = state.T2 = state.T3 = state.T4 = state.T5 = '' 84 | } 85 | }, 86 | actions: { 87 | socket_connect (context) { 88 | setTimeout(() => { 89 | context.commit('SET_WS_CONNECTED', true) 90 | try { 91 | this._vm.$socket.emit('OracConnect') 92 | } catch (err) { 93 | this.a._vm.$socket.emit('OracConnect') 94 | } 95 | }, 500) 96 | }, 97 | socket_ORAC_CONNECTED: (context) => { 98 | setTimeout(() => { 99 | context.commit('SET_ORAC_CONNECTED', true) 100 | }, 500) 101 | }, 102 | socket_P1Desc: (context, desc) => { 103 | context.commit('SET_FIELD', ['P1', 'Desc', desc]) 104 | }, 105 | socket_P1Ctrl: (context, ctrl) => { 106 | context.commit('SET_FIELD', ['P1', 'Ctrl', ctrl]) 107 | }, 108 | socket_P1Value: (context, value) => { 109 | context.commit('SET_FIELD', ['P1', 'Value', value]) 110 | }, 111 | socket_P2Desc: (context, desc) => { 112 | context.commit('SET_FIELD', ['P2', 'Desc', desc]) 113 | }, 114 | socket_P2Ctrl: (context, ctrl) => { 115 | context.commit('SET_FIELD', ['P2', 'Ctrl', ctrl]) 116 | }, 117 | socket_P2Value: (context, value) => { 118 | context.commit('SET_FIELD', ['P2', 'Value', value]) 119 | }, 120 | socket_P3Desc: (context, desc) => { 121 | context.commit('SET_FIELD', ['P3', 'Desc', desc]) 122 | }, 123 | socket_P3Ctrl: (context, ctrl) => { 124 | context.commit('SET_FIELD', ['P3', 'Ctrl', ctrl]) 125 | }, 126 | socket_P3Value: (context, value) => { 127 | context.commit('SET_FIELD', ['P3', 'Value', value]) 128 | }, 129 | socket_P4Desc: (context, desc) => { 130 | context.commit('SET_FIELD', ['P4', 'Desc', desc]) 131 | }, 132 | socket_P4Ctrl: (context, ctrl) => { 133 | context.commit('SET_FIELD', ['P4', 'Ctrl', ctrl]) 134 | }, 135 | socket_P4Value: (context, value) => { 136 | context.commit('SET_FIELD', ['P4', 'Value', value]) 137 | }, 138 | socket_P5Desc: (context, desc) => { 139 | context.commit('SET_FIELD', ['P5', 'Desc', desc]) 140 | }, 141 | socket_P5Ctrl: (context, ctrl) => { 142 | context.commit('SET_FIELD', ['P5', 'Ctrl', ctrl]) 143 | }, 144 | socket_P5Value: (context, value) => { 145 | context.commit('SET_FIELD', ['P5', 'Value', value]) 146 | }, 147 | socket_P6Desc: (context, desc) => { 148 | context.commit('SET_FIELD', ['P6', 'Desc', desc]) 149 | }, 150 | socket_P6Ctrl: (context, ctrl) => { 151 | context.commit('SET_FIELD', ['P6', 'Ctrl', ctrl]) 152 | }, 153 | socket_P6Value: (context, value) => { 154 | context.commit('SET_FIELD', ['P6', 'Value', value]) 155 | }, 156 | socket_P7Desc: (context, desc) => { 157 | context.commit('SET_FIELD', ['P7', 'Desc', desc]) 158 | }, 159 | socket_P7Ctrl: (context, ctrl) => { 160 | context.commit('SET_FIELD', ['P7', 'Ctrl', ctrl]) 161 | }, 162 | socket_P7Value: (context, value) => { 163 | context.commit('SET_FIELD', ['P7', 'Value', value]) 164 | }, 165 | socket_P8Desc: (context, desc) => { 166 | context.commit('SET_FIELD', ['P8', 'Desc', desc]) 167 | }, 168 | socket_P8Ctrl: (context, ctrl) => { 169 | context.commit('SET_FIELD', ['P8', 'Ctrl', ctrl]) 170 | }, 171 | socket_P8Value: (context, value) => { 172 | context.commit('SET_FIELD', ['P8', 'Value', value]) 173 | } 174 | }, 175 | getters: { 176 | wsConnected: state => { 177 | return state.wsConnected 178 | }, 179 | oracConnected: state => { 180 | return state.oracConnected 181 | }, 182 | page: state => { 183 | return state.page 184 | }, 185 | module: state => { 186 | return state.module 187 | }, 188 | text: state => { 189 | return [state.T1, state.T2, state.T3, state.T4, state.T5] 190 | }, 191 | selectText: state => { 192 | return state.selectText 193 | }, 194 | getField: (state) => (param, field) => { 195 | return state[param + field] 196 | } 197 | } 198 | }) 199 | 200 | export default store 201 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsedleckas/orac-control-web/b42f20da27075c6265dcdc5bde42fbe822adf72f/static/.gitkeep -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsedleckas/orac-control-web/b42f20da27075c6265dcdc5bde42fbe822adf72f/static/favicon.ico -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsedleckas/orac-control-web/b42f20da27075c6265dcdc5bde42fbe822adf72f/static/logo.png -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Orac Control", 3 | "short_name": "Orac", 4 | "description": "Web Client for Orac", 5 | "icons": [ 6 | { 7 | "src": "/static/logo.png", 8 | "sizes": "512x512" 9 | } 10 | ], 11 | "display": "fullscreen", 12 | "start_url": "/", 13 | "orientation": "landscape" 14 | } -------------------------------------------------------------------------------- /static/site.css: -------------------------------------------------------------------------------- 1 | /*@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');*/ 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | .spinner-border{ 8 | width: 1rem; 9 | height: 1rem; 10 | } 11 | 12 | .noselect { 13 | -webkit-touch-callout: none; /* iOS Safari */ 14 | -webkit-user-select: none; /* Safari */ 15 | -khtml-user-select: none; /* Konqueror HTML */ 16 | -moz-user-select: none; /* Firefox */ 17 | -ms-user-select: none; /* Internet Explorer/Edge */ 18 | user-select: none; /* Non-prefixed version, currently 19 | supported by Chrome and Opera */ 20 | } 21 | 22 | .modal-container .modal-content, 23 | .modal-container .modal-backdrop { 24 | height: 0; 25 | width: 0; 26 | opacity: 0; 27 | visibility: hidden; 28 | overflow: hidden; 29 | cursor: pointer; 30 | transition: opacity 0.2s ease-in; 31 | } 32 | 33 | .modal-container #modal-toggle { 34 | display: none; 35 | } 36 | 37 | .modal-container #modal-toggle.active ~ .modal-backdrop, .modal-container #modal-toggle:checked ~ .modal-backdrop { 38 | background-color: rgba(0, 0, 0, 0.6); 39 | width: 100vw; 40 | height: 100vh; 41 | position: fixed; 42 | left: 0; 43 | top: 0; 44 | z-index: 9; 45 | visibility: visible; 46 | opacity: 1; 47 | transition: opacity 0.2s ease-in; 48 | } 49 | 50 | .modal-container #modal-toggle.active ~ .modal-content, .modal-container #modal-toggle:checked ~ .modal-content { 51 | opacity: 1; 52 | width: 300px; 53 | height: 301px; 54 | position: fixed; 55 | left: calc(50% - 150px); 56 | top: calc(50% - 150px); 57 | z-index: 999; 58 | pointer-events: auto; 59 | cursor: auto; 60 | visibility: visible; 61 | box-shadow: 0 3px 7px rgba(0, 0, 0, 0.6); 62 | } 63 | 64 | .btn:focus, .btn:active:focus, .btn.active:focus, 65 | .btn.focus, .btn:active.focus, .btn.active.focus { 66 | outline: none; 67 | box-shadow: none; 68 | } 69 | 70 | .container-centered { 71 | display: grid; 72 | justify-content: center; 73 | align-items: center; 74 | height: 100vh; 75 | } 76 | 77 | .param-block { 78 | display: inline-block; 79 | position: relative; 80 | overflow: hidden; 81 | padding: 5px; 82 | } 83 | 84 | .param-text { 85 | white-space: nowrap; 86 | overflow: hidden; 87 | text-overflow: ellipsis; 88 | display: block; 89 | } 90 | 91 | .params-container { 92 | margin: 0 auto; 93 | } 94 | 95 | @media (min-width: 576px) { 96 | .params-container .param-block { 97 | width: 25%; 98 | } 99 | } 100 | 101 | @media (max-width: 575px) { 102 | .params-container .param-block { 103 | width: 50%; 104 | } 105 | } 106 | 107 | .logo { 108 | height: 180px; 109 | margin: 0 auto; 110 | } 111 | 112 | .btn-arrow { 113 | max-width: 60px; 114 | } 115 | 116 | .list-group-item.active { 117 | z-index: 2; 118 | color: #fff; 119 | background-color: #ff0000; 120 | border-color: #ff0000; 121 | } --------------------------------------------------------------------------------