├── .gitignore ├── R └── startup.R ├── README.md ├── bower.json ├── build ├── icon-inverse.svg ├── icon.icns ├── icon.ico ├── icon.svg └── install-spinner.gif ├── components ├── detail.html ├── grid.html ├── history.html ├── panel.html ├── shell-preferences.html ├── split-panel.html └── virtual-list.html ├── core ├── data-cache.js ├── extendR.js ├── file-cache.js ├── menus.js ├── messages.js ├── package-manager.js ├── renderer.js ├── search-history.js ├── settings.js └── utils.js ├── data ├── codemirror-minimal.css ├── menus.json └── messages.json ├── ext ├── cogs │ ├── README.md │ ├── cogs.css │ ├── cogs.eot │ ├── cogs.svg │ ├── cogs.ttf │ └── cogs.woff └── material │ ├── MaterialIcons-Regular.ijmap │ ├── MaterialIcons-Regular.woff2 │ ├── README.md │ ├── codepoints │ └── material-icons.css ├── gulpfile.js ├── html └── index.html ├── main └── main.js ├── package.json ├── packages ├── README.md ├── browser │ ├── browser.html │ ├── browser.js │ └── package.json ├── cran │ ├── cran.html │ ├── cran.js │ └── package.json ├── download │ ├── download.R │ ├── download.js │ └── package.json ├── file-watcher │ ├── file-watcher.js │ └── package.json ├── graphics │ ├── graphics-device.js │ ├── graphics-panel.html │ └── package.json ├── histogram-view │ ├── histogram-view.js │ ├── histogram.html │ └── package.json ├── html-dialog │ ├── dialog.html │ ├── html-dialog.js │ └── package.json ├── pager │ ├── package.json │ └── pager.js ├── progress │ ├── package.json │ ├── progress.R │ └── progress.js ├── side-panel │ ├── package.json │ └── side-panel.js ├── table │ ├── package.json │ └── table.js └── theme │ ├── help │ ├── bluesans.css │ └── sans.css │ ├── package.json │ ├── shell │ ├── dark.css │ ├── elegant.css │ ├── night.css │ ├── old-school.css │ └── zenburn.css │ ├── theme.js │ └── ui │ └── dark.css ├── postcss ├── button.css ├── checkbox2.css ├── chooser.css ├── dialog.css ├── grid.css ├── history.css ├── locals.css ├── main.css ├── panel.css ├── progress.css ├── scrollbars.css └── textbox.css └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | dist 4 | app 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ConstructR 2 | ========== 3 | 4 | ![ConstructR logo][logo] 5 | 6 | Electron-based R shell. 7 | 8 | Install 9 | ======= 10 | 11 | To install and run without building from scratch, grab an installer 12 | for your platform from the [releases][5] page. 13 | 14 | 15 | Build 16 | ===== 17 | 18 | ### Prerequisites ### 19 | 20 | * git https://git-scm.com 21 | 22 | * node (and npm) https://nodejs.org 23 | 24 | * R https://www.r-project.org/ 25 | 26 | Either your distribution's R package or R built from source. 27 | If you build from source, make sure to use the configure option 28 | `--enable-R-shlib` to build shared libraries. On Windows, the 29 | binary install of R is fine. 30 | 31 | R must be visible on your PATH for the build to succeed. 32 | 33 | * Build tools: the commands below will build a native node add-on 34 | ([controlr][1]) and a binary R package ([jsclientlib][2]). You 35 | need standard build tools for C/C++. On Windows, the R package 36 | will be installed as a prebuilt binary, so you don't need the 37 | [Rtools][3] package. 38 | 39 | ### Download, Build and Run ### 40 | 41 | Note: on Windows, these instructions will work if you are using a 42 | unix-like shell (like [Git bash][4]). If you are 43 | using the Windows shell ("Command Prompt"), you have to turn around 44 | the slashes. See below. 45 | 46 | ```bash 47 | # download 48 | git clone https://github.com/sdllc/constructr.git 49 | 50 | # install packages 51 | cd constructr 52 | npm install 53 | 54 | # install modules and build. 55 | node_modules/.bin/gulp 56 | 57 | # build and install the `jsclientlib` R library 58 | node_modules/.bin/gulp jsclientlib 59 | 60 | # now run 61 | node_modules/.bin/electron app 62 | 63 | # alternatively, run with livereload: 64 | # node_modules/.bin/gulp watch 65 | ``` 66 | 67 | Windows shell version: 68 | ```bat 69 | REM * download 70 | git clone https://github.com/sdllc/constructr.git 71 | 72 | REM * install packages 73 | cd constructr 74 | npm install 75 | 76 | REM * install modules and build. 77 | node_modules\.bin\gulp 78 | 79 | REM * build and install the `jsclientlib` R library 80 | node_modules\.bin\gulp jsclientlib 81 | 82 | REM * now run 83 | node_modules\.bin\electron app 84 | 85 | REM * alternatively, run with livereload: 86 | REM * node_modules\.bin\gulp watch 87 | ``` 88 | 89 | [logo]: https://cdn.rawgit.com/sdllc/constructr/master/build/icon-inverse.svg 90 | 91 | [1]: https://github.com/sdllc/controlr 92 | [2]: https://github.com/sdllc/jsclientlib 93 | [3]: https://cran.r-project.org/bin/windows/Rtools/ 94 | [4]: https://git-scm.com 95 | [5]: https://github.com/sdllc/constructr/releases 96 | 97 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editr", 3 | "description": "editr", 4 | "main": "app/main.js", 5 | "moduleType": [], 6 | "homepage": "", 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "bower_components", 11 | "test", 12 | "tests" 13 | ], 14 | "dependencies": { 15 | "polymer": "Polymer/polymer#^1.3.1", 16 | "codemirror": "^5.15.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/build/icon.ico -------------------------------------------------------------------------------- /build/install-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/build/install-spinner.gif -------------------------------------------------------------------------------- /components/detail.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 56 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /components/history.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 85 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /components/panel.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 31 | 42 | 43 | 44 | 45 | 76 | 89 | -------------------------------------------------------------------------------- /components/shell-preferences.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 29 | 30 | 47 | 48 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 111 | 112 | 167 | 168 | 169 | 170 | 171 | 195 | 196 | 224 | 225 | 226 | 227 | 228 | 229 | 250 | 251 | 284 | 285 | 286 | 287 | 288 | 289 | 296 | 330 | 331 | 332 | 333 | 334 | 342 | 343 | 354 | 355 | 356 | 357 | 358 | 359 | 402 | 412 | 413 | 414 | 415 | -------------------------------------------------------------------------------- /components/virtual-list.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 54 | 55 | 56 | 57 | 70 | 71 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /core/data-cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | /** 26 | * utility: temporary cache for package list, mirrors, and similar. 27 | * currently using localStorage, but we should think about swapping 28 | * in file storage (as an option). 29 | * 30 | * actually we might alternatively use in-memory storage: the only 31 | * difference would be on startup. 32 | */ 33 | function DataCache(R){ 34 | 35 | /** 36 | * test if data is available 37 | */ 38 | this.have_cached_data = function( key, max_cache_time ){ 39 | 40 | if( typeof max_cache_time === "undefined" ) max_cache_time = DEFAULT_CACHE_TIME; 41 | var cached = localStorage.getItem( key ); 42 | if( cached ){ 43 | cached = JSON.parse(cached); 44 | if( Date.now() - cached.retrieved > max_cache_time ){ 45 | return false; 46 | } 47 | return true; 48 | } 49 | return false; 50 | 51 | }; 52 | 53 | /** 54 | * generic cache/expire/fetch routine 55 | */ 56 | this.get_cached_data = function( key, max_cache_time, r_cmd, scrub ){ 57 | 58 | var cached = localStorage.getItem( key ); 59 | if( cached ){ 60 | cached = JSON.parse(cached); 61 | if( Date.now() - cached.retrieved > max_cache_time ){ 62 | // console.info( `${key}: flushing cache` ); 63 | localStorage.removeItem( key ); 64 | } 65 | else { 66 | // console.info( "using cache" ); 67 | return new Promise( function( resolve, reject ){ resolve(cached.data); }) 68 | } 69 | } 70 | 71 | return new Promise( function( resolve, reject ){ 72 | R.queued_internal( r_cmd ).then( function( obj ){ 73 | if( obj.response ){ 74 | var data = obj.response; 75 | if( scrub ) data = scrub.call( this, data ); 76 | localStorage.setItem( key, JSON.stringify({ 77 | retrieved: Date.now(), 78 | data: data 79 | })); 80 | resolve(data); 81 | } 82 | else reject(); 83 | }).catch( function(e){ 84 | reject(e); 85 | }); 86 | }); 87 | 88 | }; 89 | 90 | /** 91 | * transpose a frame, where the data is named. 92 | */ 93 | this.transpose_frame = function(frame){ 94 | 95 | var keys = Object.keys(frame); 96 | var len = frame[keys[0]].length; 97 | var rslt = new Array( len ); 98 | for( var i = 0; i< len; i++ ){ 99 | var o = {}; 100 | keys.map( function( key ){ 101 | o[key] = frame[key][i]; 102 | }); 103 | rslt[i] = o; 104 | } 105 | return rslt; 106 | }; 107 | 108 | /** 109 | * matrices are just vectors with row counts (and optionally dimnames) 110 | * FIXME: this should go into some data management class 111 | */ 112 | this.build_matrix = function(data, nrow, ncol, rowdominant){ 113 | 114 | var cols = new Array(ncol); 115 | for( var c = 0; c< ncol; c++ ){ 116 | var rows = data.splice( 0, nrow ); 117 | cols[c] = rows ; 118 | } 119 | if( rowdominant ) return this.transpose_array( cols ); 120 | return cols; 121 | }; 122 | 123 | /** 124 | * utility method 125 | */ 126 | this.apply_names = function( mat, order, names ){ 127 | if( order === 1 ){ 128 | var rslt = new Array( mat.length ); 129 | for( var i = 0; i< mat.length; i++ ){ 130 | var o = {}; 131 | for( var j = 0; j< names.length; j++ ){ 132 | o[names[j]] = mat[i][j]; 133 | } 134 | rslt[i] = o; 135 | } 136 | return rslt; 137 | } 138 | } 139 | 140 | /** 141 | * transpose an array [[]] that we get from R. matrices and data 142 | * frames are column-dominant, for our purposes we (sometimes) 143 | * want row-dominant arrays. 144 | */ 145 | this.transpose_array = function(arr){ 146 | 147 | // NOTE: we expect this to be regular (i.e. no short columns) 148 | 149 | var len1 = arr.length; 150 | var len2 = arr[0].length; 151 | 152 | var rslt = new Array( len2 ); 153 | for( var i = 0; i< len2; i++ ){ 154 | var row = new Array( len1 ); 155 | for( var j = 0; j< len1; j++ ){ 156 | row[j] = arr[j][i]; 157 | } 158 | rslt[i] = row; 159 | } 160 | 161 | return rslt; 162 | } 163 | 164 | }; 165 | 166 | // FIXME: configurable 167 | DataCache.prototype.DEFAULT_CACHE_TIME = ( 1000 * 60 * 120 ); 168 | 169 | module.exports = DataCache; 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /core/extendR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const path = require( "path" ); 26 | //const ControlR = require( "controlr" ); 27 | const ControlR = require( "../../controlr/js/controlr.js" ); 28 | 29 | /** 30 | * controlR is the basic R wrapper. here we're adding some convenience 31 | * methods and functions that are useful for our UI, but aren't core 32 | * functionality (and hence aren't included in controlR). 33 | */ 34 | var ExtendR = function(){ 35 | 36 | // fixme: utility lib 37 | 38 | /** 39 | * enquote with double quotes; escape other quotes. 40 | * 41 | * FIXME: is this correct? what if the string already has escaped quotes 42 | * in it (called twice, for example)? should it then double-escape? 43 | */ 44 | function enquote(s){ 45 | return `"${s.replace( /"/g, '\\\"')}"`; // chrome doesn't support 'g' with string match? 46 | } 47 | 48 | // === public methods ===================================================== 49 | 50 | /** get all options */ 51 | this.options = function(){ 52 | return this.internal( `options()` ); 53 | }; 54 | 55 | /** get option */ 56 | this.get_option = function( key ){ 57 | return this.internal( `options('${key}')` ); 58 | }; 59 | 60 | /** set option. strings will be quoted. */ 61 | this.set_option = function( key, value ){ 62 | if( typeof value === "string" ) value = enquote( value ); 63 | return this.internal( `options(${key}=${value})` ); 64 | }; 65 | 66 | /** set multiple options */ 67 | this.set_options = function( kvpairs ){ 68 | 69 | var s = []; 70 | for( var key in kvpairs ){ 71 | var val = kvpairs[key]; 72 | if( typeof val === "string" ) val = enquote( val ); 73 | s.push( `${key}=${val}` ); 74 | } 75 | var cmd = `options(${s.join( "," )})`; 76 | console.info(cmd); 77 | return this.internal(cmd); 78 | 79 | }; 80 | 81 | /** 82 | * set cran mirror, which has to be in a list. this is in an 83 | * anonymous function to prevent leaving any detritus in the environment 84 | */ 85 | this.set_cran_mirror = function( mirror, message ){ 86 | 87 | mirror = mirror ? enquote( mirror ) : "NULL" ; 88 | 89 | var cmd = [ 90 | `(function(){ repos <- getOption('repos')`, 91 | `repos['CRAN'] <- ${mirror}`, 92 | `options(repos = repos)` 93 | ]; 94 | 95 | if( message ) cmd.push( 96 | `cat('${message}\n')` 97 | ); 98 | 99 | cmd.push( `})()` ); 100 | return this.internal( cmd ); 101 | 102 | }; 103 | 104 | /** get cran mirror (shortcut) */ 105 | this.get_cran_mirror = function(){ 106 | var instance = this; 107 | return new Promise( function( resolve, reject ){ 108 | instance.internal( "getOption('repos')").then( function( rslt ){ 109 | if( rslt && rslt.response ){ 110 | if( typeof rslt.response === "string" ){ 111 | resolve( rslt.response.repos ); 112 | } 113 | else if( rslt.response.CRAN ){ 114 | resolve( rslt.response.CRAN ); 115 | } 116 | else reject("NULL or not set"); 117 | } 118 | else reject("NULL or not set"); 119 | }).catch( function(e){ 120 | reject(e); 121 | }); 122 | }); 123 | }; 124 | 125 | /** 126 | * set console width (in characters) 127 | */ 128 | this.set_console_width = function(chars){ 129 | this.internal( `options(width=${chars})`, "set.console.width" ); 130 | }; 131 | 132 | // === constructor ======================================================== 133 | 134 | // superclass init 135 | ControlR.apply( this, arguments ); 136 | 137 | }; 138 | 139 | // inherit prototype methods 140 | for (var x in ControlR.prototype){ 141 | ExtendR.prototype[x] = ControlR.prototype[x]; 142 | } 143 | 144 | module.exports = ExtendR; -------------------------------------------------------------------------------- /core/file-cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | var fs = require( "fs" ); 26 | 27 | /** 28 | * utility: cache file contents and refresh on mtime. 29 | */ 30 | var FileCache = function(){ 31 | 32 | var store = {}; 33 | 34 | var cache = function( path ){ 35 | return new Promise( function( resolve, reject ){ 36 | fs.readFile( path, { encoding: "utf8" }, function( err, contents ){ 37 | if( err ) reject( err ); 38 | if( !contents ) contents = ""; 39 | store[path] = { 40 | contents: contents, 41 | cache_time: new Date().getTime() 42 | }; 43 | resolve( contents ); 44 | }); 45 | }); 46 | }; 47 | 48 | this.ensure = function( path ){ 49 | return new Promise( function( resolve, reject ){ 50 | var cached = store[path]; 51 | if( typeof cached !== "undefined" ){ 52 | fs.stat( path, function(err, stats){ 53 | if( err ) reject( err ); 54 | if( stats.mtime.getTime() > cached.cache_time ){ 55 | cache( path ).then( function( contents ){ 56 | resolve(contents); 57 | }).catch( function( err ){ reject(err); }); 58 | } 59 | else { 60 | resolve( cached.contents ); 61 | } 62 | }); 63 | } 64 | else { 65 | cache( path ).then( function( contents ){ 66 | resolve(contents); 67 | }).catch( function( err ){ reject(err); }); 68 | } 69 | }); 70 | }; 71 | 72 | }; 73 | 74 | module.exports = FileCache; 75 | 76 | -------------------------------------------------------------------------------- /core/menus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /** 24 | * this is a layer on top of electron's existing menu template 25 | * scheme that adds a few features we specifically want. 26 | */ 27 | 28 | "use strict"; 29 | 30 | const PubSub = require( "pubsub-js" ); 31 | const fs = require( "fs" ); 32 | const path = require( "path" ); 33 | const Menu = require('electron').remote.Menu; 34 | 35 | /** 36 | * (1) remove any elements that have a "platform" attribute which 37 | * does not match the current platform. 38 | * 39 | * UPDATE: we now also support !platform (gyp style) 40 | * 41 | * (2) translate elements with a platform suffix if the platform 42 | * matches. 43 | * 44 | * (3) if an element has a "message" attribute, construct a click 45 | * function. 46 | */ 47 | function build_menu_template( src, name, click ){ 48 | 49 | if( !click ) click = function(message){ 50 | PubSub.publish( "menu-click", { 51 | menu: name, 52 | message: message, 53 | item: arguments[1], 54 | focusedWindow: arguments[2] 55 | }); 56 | }; 57 | 58 | var platform_key = "-" + process.platform; 59 | return src.filter( function( entry ){ 60 | 61 | var keys = Object.keys( entry ); 62 | keys.forEach( function( key ){ 63 | if( key.endsWith( platform_key )){ 64 | entry[key.substr( 0, key.length - platform_key.length )] = entry[key]; 65 | } 66 | }); 67 | 68 | for( var key in entry ){ 69 | if(( key === "platform" && entry[key] !== process.platform ) 70 | || ( key === "!platform" && entry[key] === process.platform )){ 71 | return undefined; 72 | } 73 | else { 74 | switch( key ){ 75 | case "submenu": 76 | entry[key] = build_menu_template( entry[key], name, click ); 77 | break; 78 | case "message": 79 | entry.click = click.bind( this, entry.message ); 80 | break; 81 | } 82 | } 83 | } 84 | return entry; 85 | }); 86 | 87 | } 88 | 89 | var MenuTemplate = function(path){ 90 | 91 | try { 92 | this._template = fs.readFileSync(path, { encoding: "utf8" }); 93 | if( this._template ){ 94 | this._template = JSON.parse( this._template ); 95 | var keys = Object.keys( this._template ); 96 | keys.forEach( function( key ){ 97 | this[key] = Menu.buildFromTemplate( build_menu_template( 98 | this._template[key], key 99 | )); 100 | }, this); 101 | } 102 | } 103 | catch( e ){ 104 | console.error(e); 105 | } 106 | 107 | }; 108 | 109 | module.exports = MenuTemplate; 110 | -------------------------------------------------------------------------------- /core/messages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | /** 26 | * load messages from json file. placeholder for i18n. 27 | */ 28 | 29 | const fs = require( "fs" ); 30 | 31 | /** array -> multiline string */ 32 | var concatenate_strings = function( obj ){ 33 | var keys = Object.keys(obj); 34 | keys.forEach( function( key ){ 35 | var val = obj[key]; 36 | if( Array.isArray( val )) obj[key] = val.join( "\n" ); 37 | else if( typeof val === "object" ) obj[key] = concatenate_strings(val); 38 | }); 39 | return obj; 40 | } 41 | 42 | /** singleton */ 43 | var Messages = function(){ 44 | this.load = function(path){ 45 | var contents = fs.readFileSync( path, { encoding: "utf8" }); 46 | if( !contents ) return {}; 47 | try { 48 | contents = JSON.parse( contents ); 49 | return concatenate_strings( contents ); 50 | } 51 | catch( e ){ 52 | console.err( e ); 53 | } 54 | return {}; 55 | }; 56 | }; 57 | 58 | module.exports = new Messages(); 59 | 60 | -------------------------------------------------------------------------------- /core/package-manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const path = require( 'path' ); 26 | const fs = require( 'fs' ); 27 | 28 | const X = eval( 'require' ); // !webpack 29 | 30 | window.fs = fs; 31 | 32 | var PackageManager = function(){ 33 | 34 | /** loaded packages, by name */ 35 | this.packages = {}; 36 | 37 | /** pending packages (missing deps) */ 38 | this.pending = []; 39 | 40 | /** hold on to spec */ 41 | this.spec = {}; 42 | 43 | /** 44 | * list packages, concatenate name 45 | */ 46 | this.list_packages = function( package_dir ){ 47 | return new Promise( function( resolve, reject ){ 48 | fs.readdir( package_dir, function(err, files){ 49 | resolve(files.map( function( file ){ 50 | return path.join( package_dir, file ); 51 | })); 52 | }); 53 | }); 54 | }; 55 | 56 | /** 57 | * consolidate list/load 58 | */ 59 | this.load_packages = function( dir, core, opts ){ 60 | let instance = this; 61 | return new Promise( function( resolve, reject ){ 62 | instance.list_packages( dir ).then( function( list ){ 63 | return instance.load_packages_list( list, core, opts ); 64 | }).then( function(){ 65 | resolve(); 66 | }).catch( function(e){ 67 | reject(e); 68 | }); 69 | }); 70 | }; 71 | 72 | /** 73 | * load packages, recursively, from list. 74 | * added dependencies. todo: versioned deps 75 | * 76 | * FIXME: this is turning into spaghetti. refactor. 77 | */ 78 | this.load_packages_list = function( list, core, opts ){ 79 | 80 | let self = this; 81 | 82 | opts = opts || {}; 83 | if( !Array.isArray( list )) list = [list]; 84 | 85 | let platformkey = "default." + process.platform; 86 | let default_settings = function( prefs ){ 87 | for( let key in prefs ){ 88 | if( typeof core.Settings[key] === "undefined" ) 89 | { 90 | if( typeof prefs[key][platformkey] !== "undefined" ) 91 | core.Settings[key] = prefs[key][platformkey]; 92 | else if( typeof prefs[key].default !== "undefined" ) 93 | core.Settings[key] = prefs[key].default; 94 | } 95 | } 96 | }; 97 | 98 | let init_package_js = function( pkg, elt ){ 99 | 100 | console.info( "Installing package", pkg.name ); 101 | 102 | // if this package has options, with defaults, 103 | // enforce defaults. 104 | if( pkg.preferences ) default_settings( pkg.preferences ); 105 | if( pkg.preferenceGroups ){ 106 | for( let group in pkg.preferenceGroups ){ 107 | default_settings( pkg.preferenceGroups[group] ); 108 | } 109 | } 110 | 111 | // load html files (polymer components) 112 | if( pkg.htmlComponents ){ 113 | 114 | // don't blindly do that; in this case, we _want_ files from the archive 115 | 116 | //let source_dir = core.Utils.escape_backslashes(core.Utils.patch_asar_path(elt), 2); 117 | let source_dir = core.Utils.escape_backslashes(elt, 2); 118 | pkg.htmlComponents.forEach( function(component){ 119 | core.Utils.install_html_component( 120 | path.join( source_dir, component )); 121 | }) 122 | } 123 | 124 | // ok, load and call init 125 | self.packages[ pkg.name ] = pkg.module; 126 | self.spec[ pkg.name ] = pkg; 127 | 128 | // can use a different method name (?) 129 | var func = "init"; 130 | if( pkg.init ) func = pkg.init; 131 | 132 | if( pkg.module[func] ){ 133 | let rslt = pkg.module[func].call( this, core); 134 | return rslt ? rslt : Promise.resolve(); 135 | } 136 | else return Promise.resolve(); 137 | 138 | }; 139 | 140 | let init_package = function( pkg, elt ){ 141 | return new Promise( function( resolve, reject ){ 142 | init_package_js( pkg, elt ).then( function(){ 143 | if( pkg.R ){ 144 | let source_dir = core.Utils.escape_backslashes( 145 | core.Utils.patch_asar_path(elt), 2); 146 | let source_file = core.Utils.escape_backslashes( 147 | core.Utils.patch_asar_path(path.join( elt, pkg.R )), 2 ); 148 | let cmd = `.dirname <- "${source_dir}"; source("${source_file}"); rm(.dirname);`; 149 | return core.R.exec( cmd ); 150 | } 151 | else return Promise.resolve(); 152 | }).then( function(){ 153 | resolve(); 154 | }); 155 | }); 156 | }; 157 | 158 | return new Promise( function( resolve, reject ){ 159 | if( list.length ){ 160 | let elt = list.shift(); 161 | self.load( elt ).then( function( pkg, err ){ 162 | if( pkg ){ 163 | 164 | // don't reload if we have already loaded 165 | // (UPDATE: argument) 166 | if( !opts.allow_override && self.packages[ pkg.name ]) return Promise.resolve(); 167 | 168 | // check deps 169 | if( pkg.packageDependencies ){ 170 | let deps = Array.isArray( pkg.packageDependencies ) ? 171 | pkg.packageDependencies : Object.keys( pkg.packageDependencies ); 172 | if( deps.some( function( dep ){ 173 | return !self.packages[ dep ]; 174 | })){ 175 | pkg.__dirname = elt; 176 | self.pending.push( pkg ); 177 | return Promise.resolve(); 178 | } 179 | } 180 | return init_package( pkg, elt ); 181 | 182 | 183 | } 184 | return Promise.resolve(); 185 | }).then( function(){ 186 | return self.load_packages_list( list, core, opts ); 187 | }).then( function(){ 188 | resolve(); 189 | }); 190 | } 191 | else { 192 | 193 | if( self.pending.length ){ 194 | for( let i = 0; i< self.pending.length; i++ ){ 195 | let pkg = self.pending[i]; 196 | let deps = Array.isArray( pkg.packageDependencies ) ? 197 | pkg.packageDependencies : Object.keys( pkg.packageDependencies ); 198 | if( deps.every( function( dep ){ 199 | return !!self.packages[ dep ]; 200 | })){ 201 | self.pending.splice( i, 1 ); 202 | init_package( pkg, pkg.__dirname ).then( function(){ 203 | return self.load_packages_list( list, core, opts ); 204 | }).then( function(){ 205 | resolve(); 206 | }); 207 | return; 208 | } 209 | } 210 | if( self.pending.length ){ 211 | let unresolvable = self.pending.map( function( pkg ){ 212 | return pkg.name; 213 | }); 214 | console.info( "Some packages have unresolvable dependencies:" ); 215 | self.pending.map( function( pkg ){ 216 | console.info( pkg.name + ":", pkg.packageDependencies ); 217 | }); 218 | } 219 | } 220 | resolve(); 221 | 222 | } 223 | }); 224 | }; 225 | 226 | /** 227 | * load a pkg 228 | */ 229 | this.load = function(dir){ 230 | return new Promise( function( resolve, reject ){ 231 | 232 | // check for a pkg file 233 | var package_file = path.join( dir, "package.json" ); 234 | fs.readFile( package_file, { encoding: "utf8" }, (err, contents) => { 235 | if (err){ 236 | resolve( false, err ); 237 | return; 238 | } 239 | try { 240 | var spec = JSON.parse( contents ); 241 | if( !spec.main ) throw( "package main not found" ); 242 | if( !spec.name ) throw( "package name not found" ); 243 | console.info( "Loading package", spec.name); 244 | spec.module = X(path.join( dir, spec.main )); 245 | resolve(spec); 246 | } 247 | catch( err ){ 248 | console.error( err ); 249 | resolve( false, err ); 250 | } 251 | }); 252 | 253 | }); 254 | }; 255 | 256 | }; 257 | 258 | module.exports = new PackageManager(); 259 | -------------------------------------------------------------------------------- /core/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /** 24 | * dictionary object with a backing store (currently localStorage, 25 | * but should be pluggable) that broadcasts changes. 26 | */ 27 | 28 | "use strict"; 29 | 30 | const PubSub = require( "pubsub-js" ); 31 | 32 | const LocalStorageBase = function(key){ 33 | 34 | if( !key ) throw( "provide a key for localStorage" ); 35 | Object.defineProperty( this, "__storage_key__", { 36 | enumerable: false, 37 | configurable: false, 38 | value: key 39 | }); 40 | 41 | let json = localStorage.getItem( key ); 42 | let js = json ? JSON.parse( json ) : {}; 43 | Object.assign( this, js ); 44 | 45 | }; 46 | 47 | LocalStorageBase.prototype.save = function(){ 48 | localStorage.setItem( this.__storage_key__, JSON.stringify( this )); 49 | } 50 | 51 | const Settings = new Proxy( new LocalStorageBase( "settings" ), { 52 | 53 | set: function(target, property, value, receiver) { 54 | 55 | if( typeof value === "undefined" || null === value ) delete target[property]; 56 | else target[property] = value; 57 | 58 | // save to backing store 59 | target.save(); 60 | 61 | // broadcast 62 | PubSub.publish( "settings-change", { key: property, val: value }); 63 | 64 | return true; 65 | }, 66 | 67 | /** 68 | * NOTE: we're deep-copying here. why? to prevent accidental update 69 | * of object values. There's a case in which you get an object 70 | * 71 | * let x = Settings['x'] 72 | * 73 | * and then modify that object, 74 | * 75 | * x.y = 1 76 | * 77 | * which has the effect of calling set() on the Settings object because 78 | * it's a reference. while that might be useful behavior, it's unintuitive 79 | * and different from non-object behavior. therefore we use copies. 80 | */ 81 | get: function(target, property, receiver) { 82 | let rslt = target[property]; 83 | if( typeof rslt === "object"){ 84 | // cheaper way? 85 | rslt = JSON.parse( JSON.stringify( rslt )); 86 | } 87 | return rslt; 88 | } 89 | 90 | }); 91 | 92 | module.exports = Settings; 93 | 94 | -------------------------------------------------------------------------------- /core/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | const DataCache = require( "./data-cache.js" ); 24 | const FileCache = require( "./file-cache.js" ); 25 | 26 | module.exports = { 27 | 28 | escape_backslashes: function(s, count){ 29 | count = count || 1; 30 | var repl = ""; 31 | for( var i = 0; i< count; i++ ) repl += '\\'; 32 | return s.replace( /\\/g, repl ); 33 | }, 34 | 35 | /** 36 | * add an import to document head. will remove/re-add. 37 | */ 38 | install_html_component: function( href ){ 39 | 40 | var nodes = document.querySelectorAll( "link[rel='import']"); 41 | for( var i = 0; i< nodes.length; i++ ){ 42 | if( nodes[i].href === href ) nodes[i].parentElement.removeChild(nodes[i]); 43 | } 44 | var node = document.createElement( "link" ); 45 | node.setAttribute( "rel", "import" ); 46 | node.setAttribute( "href", href ); 47 | document.querySelector( "head" ).appendChild( node ); 48 | 49 | }, 50 | 51 | /** 52 | * is needle === haystack, or is needle _in_ haystack, 53 | * or if need is an array, is one element of needle === or in haystack 54 | */ 55 | array_cross_match: function( haystack, needle ){ 56 | if( !Array.isArray( needle )) needle = [needle]; 57 | return needle.some( function( test ){ 58 | return ( Array.isArray( haystack ) && haystack.includes( test )) || haystack === test; 59 | }); 60 | }, 61 | 62 | /** 63 | * patch for file paths when running in a hybrid asar packed/unpacked 64 | * environment. we generally use __dirname to map paths, but that will 65 | * fail for our unpacked binaries. 66 | * 67 | * broken out to normalize. 68 | */ 69 | patch_asar_path: function( original_path ){ 70 | 71 | // (1) should almost certainly not be /g. 72 | // (2) is it guaranteed to be "app.asar"? 73 | 74 | return original_path.replace( /app\.asar/g, "app.asar.unpacked" ); 75 | 76 | }, 77 | 78 | init: function(R){ 79 | this.data_cache = new DataCache(R); 80 | this.file_cache = new FileCache(); 81 | return this; 82 | } 83 | 84 | }; 85 | 86 | 87 | -------------------------------------------------------------------------------- /data/codemirror-minimal.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * this is a cut-down version of the codemirror stylesheet. it also 4 | * includes some core styles from the constructr stylesheets. 5 | * 6 | * FIXME: automate construction 7 | */ 8 | 9 | .CodeMirror { 10 | font-family: monospace; 11 | color: black; 12 | } 13 | 14 | /* PADDING */ 15 | 16 | .CodeMirror-lines { 17 | padding: 4px 0; /* Vertical padding around content */ 18 | } 19 | .CodeMirror pre { 20 | padding: 0 4px; /* Horizontal padding of content */ 21 | } 22 | 23 | .cm-tab { display: inline-block; text-decoration: inherit; } 24 | 25 | /* DEFAULT THEME */ 26 | 27 | .cm-s-default .cm-header {color: blue;} 28 | .cm-s-default .cm-quote {color: #090;} 29 | .cm-negative {color: #d44;} 30 | .cm-positive {color: #292;} 31 | .cm-header, .cm-strong {font-weight: bold;} 32 | .cm-em {font-style: italic;} 33 | .cm-link {text-decoration: underline;} 34 | .cm-strikethrough {text-decoration: line-through;} 35 | 36 | .cm-s-default .cm-keyword {color: #708;} 37 | .cm-s-default .cm-atom {color: #219;} 38 | .cm-s-default .cm-number {color: #164;} 39 | .cm-s-default .cm-def {color: #00f;} 40 | .cm-s-default .cm-variable, 41 | .cm-s-default .cm-punctuation, 42 | .cm-s-default .cm-property, 43 | .cm-s-default .cm-operator {} 44 | .cm-s-default .cm-variable-2 {color: #05a;} 45 | .cm-s-default .cm-variable-3 {color: #085;} 46 | .cm-s-default .cm-comment {color: #a50;} 47 | .cm-s-default .cm-string {color: #a11;} 48 | .cm-s-default .cm-string-2 {color: #f50;} 49 | .cm-s-default .cm-meta {color: #555;} 50 | .cm-s-default .cm-qualifier {color: #555;} 51 | .cm-s-default .cm-builtin {color: #30a;} 52 | .cm-s-default .cm-bracket {color: #997;} 53 | .cm-s-default .cm-tag {color: #170;} 54 | .cm-s-default .cm-attribute {color: #00c;} 55 | .cm-s-default .cm-hr {color: #999;} 56 | .cm-s-default .cm-link {color: #00c;} 57 | 58 | .cm-s-default .cm-error {color: #f00;} 59 | .cm-invalidchar {color: #f00;} 60 | 61 | .CodeMirror-composing { border-bottom: 2px solid; } 62 | 63 | /* Default styles for common addons */ 64 | 65 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 66 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 67 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 68 | .CodeMirror-activeline-background {background: #e8f2ff;} 69 | 70 | /* STOP */ 71 | 72 | /* The rest of this file contains styles related to the mechanics of 73 | the editor. You probably shouldn't touch them. */ 74 | 75 | .CodeMirror { 76 | background: white; 77 | } 78 | 79 | .CodeMirror-lines { 80 | cursor: text; 81 | min-height: 1px; /* prevents collapsing before first draw */ 82 | } 83 | .CodeMirror pre { 84 | /* Reset some styles that the rest of the page might have set */ 85 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 86 | border-width: 0; 87 | background: transparent; 88 | font-family: inherit; 89 | font-size: inherit; 90 | margin: 0; 91 | white-space: pre; 92 | word-wrap: normal; 93 | line-height: inherit; 94 | color: inherit; 95 | z-index: 2; 96 | position: relative; 97 | overflow: visible; 98 | -webkit-tap-highlight-color: transparent; 99 | -webkit-font-variant-ligatures: none; 100 | font-variant-ligatures: none; 101 | } 102 | .CodeMirror-wrap pre { 103 | word-wrap: break-word; 104 | white-space: pre-wrap; 105 | word-break: normal; 106 | } 107 | 108 | .CodeMirror-linebackground { 109 | position: absolute; 110 | left: 0; right: 0; top: 0; bottom: 0; 111 | z-index: 0; 112 | } 113 | 114 | .CodeMirror-linewidget { 115 | position: relative; 116 | z-index: 2; 117 | overflow: auto; 118 | } 119 | 120 | .CodeMirror-widget {} 121 | 122 | .CodeMirror-code { 123 | outline: none; 124 | } 125 | 126 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 127 | .CodeMirror span { *vertical-align: text-bottom; } 128 | 129 | /* Used to force a border model for a node */ 130 | .cm-force-border { padding-right: .1px; } 131 | 132 | /* See issue #2901 */ 133 | .cm-tab-wrap-hack:after { content: ''; } 134 | 135 | /* --- constructr core ----------------------- */ 136 | 137 | .CodeMirror, .fixed-width { 138 | font-family: consolas, monospace; 139 | font-size: 14.25px; 140 | height: 100%; 141 | } 142 | 143 | .linux .CodeMirror, .linux .fixed-width { 144 | font-family: "Liberation Mono"; 145 | } 146 | 147 | .ubuntu .CodeMirror, .ubuntu .fixed-width { 148 | font-family: "Ubuntu Mono"; 149 | font-size: 13pt; 150 | } 151 | 152 | .osx .CodeMirror, .osx .fixed-width { 153 | font-family: "Menlo"; 154 | font-size: 13px; 155 | } 156 | 157 | .cm-s-default span.shell-parse-error { color: #ff4f3f !important; } 158 | .cm-s-default span.pager { color: #000 !important; } 159 | .cm-s-default span.shell-piped-stream { color: #000 !important; } 160 | .cm-s-default span.shell-prompt-debug { color: red; } 161 | 162 | .CodeMirror .graphics-device { 163 | margin: 1em 1em 1em 1em; 164 | border: 1px solid #ccc; 165 | width: 576px; 166 | height: 360px; 167 | } 168 | 169 | -------------------------------------------------------------------------------- /data/menus.json: -------------------------------------------------------------------------------- 1 | { 2 | "shell-context-menu": [ 3 | { 4 | "label": "Cut", 5 | "role": "cut" 6 | }, 7 | { 8 | "label": "Copy", 9 | "role": "copy" 10 | }, 11 | { 12 | "label": "Paste", 13 | "role": "paste" 14 | }, 15 | { 16 | "label": "Select All", 17 | "message": "context-select-all" 18 | }, 19 | { 20 | "type": "separator" 21 | }, 22 | { 23 | "label": "Clear Shell", 24 | "message": "clear-shell" 25 | }, 26 | { 27 | "label": "Save Shell Contents...", 28 | "message": "save-shell" 29 | }, 30 | { 31 | "label": "Shell Preferences", 32 | "message": "preferences" 33 | }, 34 | { 35 | "type": "separator" 36 | }, 37 | { 38 | "label": "Cancel" 39 | } 40 | ], 41 | "application": [ 42 | { 43 | "label": "ConstructR-Shell", 44 | "platform": "darwin", 45 | "submenu": [ 46 | { 47 | "label": "About ConstructR-Shell", 48 | "role": "about" 49 | }, 50 | { 51 | "type": "separator" 52 | }, 53 | { 54 | "label": "Hide ConstructR-Shell", 55 | "accelerator": "Command+H", 56 | "role": "hide" 57 | }, 58 | { 59 | "label": "Hide Others", 60 | "accelerator": "Command+Alt+H", 61 | "role": "hideothers" 62 | }, 63 | { 64 | "label": "Show All", 65 | "role": "unhide" 66 | }, 67 | { 68 | "type": "separator" 69 | }, 70 | { 71 | "label": "Quit", 72 | "accelerator": "Command+Q", 73 | "message": "close" 74 | } 75 | ] 76 | }, 77 | { 78 | "label": "File", 79 | "submenu": [ 80 | { 81 | "label": "Save History...", 82 | "message": "save-history" 83 | }, 84 | { 85 | "label": "Save Shell Contents as HTML...", 86 | "message": "save-shell" 87 | }, 88 | { 89 | "label": "Save Environment", 90 | "message": "save-environment" 91 | }, 92 | { "type": "separator" }, 93 | { 94 | "label": "Quit", 95 | "accelerator": "CmdOrCtrl+Q", 96 | "message": "close" 97 | } 98 | ] 99 | }, 100 | { 101 | "label": "Edit", 102 | "submenu": [ 103 | { 104 | "label": "Undo", 105 | "accelerator": "CmdOrCtrl+Z", 106 | "role": "undo" 107 | }, 108 | { 109 | "label": "Redo", 110 | "accelerator": "Shift+CmdOrCtrl+Z", 111 | "role": "redo" 112 | }, 113 | { 114 | "type": "separator" 115 | }, 116 | { 117 | "label": "Cut", 118 | "accelerator": "CmdOrCtrl+X", 119 | "role": "cut" 120 | }, 121 | { 122 | "label": "Copy", 123 | "accelerator": "CmdOrCtrl+C", 124 | "role": "copy" 125 | }, 126 | { 127 | "label": "Paste", 128 | "accelerator": "CmdOrCtrl+V", 129 | "role": "paste" 130 | }, 131 | { 132 | "label": "Select All", 133 | "accelerator": "CmdOrCtrl+A", 134 | "role": "selectall" 135 | } 136 | ] 137 | }, 138 | { 139 | "label": "View", 140 | "submenu": [ 141 | { 142 | "label": "Reload", 143 | "accelerator": "CmdOrCtrl+R", 144 | "message": "reload" 145 | }, 146 | { 147 | "label": "Toggle Full Screen", 148 | "accelerator": "F11", 149 | "accelerator-darwin": "Ctrl+Command+F", 150 | "message": "full-screen" 151 | }, 152 | { 153 | "label": "Toggle Developer Tools", 154 | "accelerator": "Ctrl+Shift+I", 155 | "accelerator-darwin": "Alt+Command+I", 156 | "message": "developer-tools" 157 | }, 158 | { 159 | "label": "Graphics Panel", 160 | "message": "graphics-panel" 161 | }, 162 | { 163 | "type": "separator" 164 | }, 165 | { 166 | "label": "Locals", 167 | "accelerator": "F9", 168 | "message": "locals" 169 | }, 170 | { 171 | "label": "Watch", 172 | "accelerator": "F8", 173 | "message": "watch" 174 | }, 175 | { 176 | "label": "History", 177 | "accelerator": "Ctrl+H", 178 | "message": "history" 179 | }, 180 | { 181 | "type": "separator" 182 | }, 183 | { 184 | "label": "Clear Shell", 185 | "accelerator": "Ctrl+Shift+X", 186 | "accelerator-darwin": "Alt+Command+X", 187 | "message": "clear-shell" 188 | }, 189 | { 190 | "label": "Toggle Shell Preferences", 191 | "accelerator": "Ctrl+Shift+P", 192 | "accelerator-darwin": "Alt+Command+P", 193 | "message": "preferences" 194 | }, 195 | { 196 | "type": "separator" 197 | }, 198 | { 199 | "label": "Choose CRAN Mirror", 200 | "accelerator": "Ctrl+Shift+M", 201 | "accelerator-darwin": "Alt+Command+M", 202 | "message": "choose-mirror" 203 | }, 204 | { 205 | "label": "Install Packages", 206 | "accelerator": "Ctrl+Shift+K", 207 | "accelerator-darwin": "Alt+Command+K", 208 | "message": "install-packages" 209 | } 210 | ] 211 | }, 212 | { 213 | "label": "Window", 214 | "role": "window", 215 | "submenu": [ 216 | { 217 | "label": "Minimize", 218 | "accelerator": "CmdOrCtrl+M", 219 | "role": "minimize" 220 | }, 221 | { 222 | "label": "Close", 223 | "accelerator": "CmdOrCtrl+W", 224 | "message": "close" 225 | }, 226 | { 227 | "label": "Bring All to Front", 228 | "role": "front", 229 | "platform": "darwin" 230 | } 231 | ] 232 | }, 233 | { 234 | "label": "&Help", 235 | "role": "help", 236 | "submenu": [ 237 | { 238 | "label": "Learn More", 239 | "message": "learn-more" 240 | }, 241 | { 242 | "label": "&About", 243 | "message": "about", 244 | "!platform": "darwin" 245 | } 246 | ] 247 | } 248 | ] 249 | } 250 | -------------------------------------------------------------------------------- /data/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "MISSING_JSCLIENTLIB": [ 3 | "WARNING: The jsClientLib library is not available. Many functions", 4 | "in the shell depend on this library, and will not work as expected.", 5 | "You can download the library from the git repository:", 6 | "", 7 | "https://github.com/sdllc/jsclientlib", 8 | "", 9 | "" 10 | ], 11 | 12 | "R_HOME_NOT_FOUND": [ 13 | "", 14 | "We can't initialize R because R_HOME was not found.", 15 | "", 16 | "You can do one of the following:", 17 | "", 18 | " * use the command line argument --r-home=/path/to/R", 19 | " * set an environment variable R_HOME", 20 | " * use a version of ControlR that bundles R", 21 | " * update your PATH to point to the R executable", 22 | "" 23 | ], 24 | 25 | "LOADING_MIRROR_LIST": "Loading mirror list, please wait...", 26 | "SET_CRAN_MIRROR": "Set CRAN mirror", 27 | 28 | "LOADING_STARTUP_FILE": "Loading startup file", 29 | 30 | "ERROR_OCCURRED": "An error occurred. Please try again later.", 31 | 32 | "INSTALLED_FLAG": "(installed)", 33 | "INSTALLING_PACKAGES": "Installing packages...\n", 34 | 35 | "LOADING_PACKAGE_LIST": "Loading package list, please wait...", 36 | "PLEASE_SELECT_MIRROR": "Please select a CRAN mirror before installing packages\n", 37 | 38 | "TRYING_URL": "Trying URL", 39 | "DOWNLOADING": "Downloading", 40 | "DOWNLOAD_COMPLETE": "Download complete", 41 | "DOWNLOAD_FAILED": "Download failed", 42 | 43 | "SAVED_OK": "Saved OK", 44 | 45 | "FIELD": "Field", 46 | "CLASS": "Class", 47 | "SIZE": "Size", 48 | "VALUE": "Value", 49 | "DETAILS": "Details", 50 | "ADD_WATCH": "Add Watch", 51 | "WATCHES": "Watches", 52 | "LOCALS": "Locals", 53 | "WATCH": "Watch", 54 | "REMOVE_WATCH": "Remove watch" 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /ext/cogs/README.md: -------------------------------------------------------------------------------- 1 | 2 | Cog icons by [Jiri Silha][1]. I found them via [Pasquale Vitiello][2]. 3 | 4 | [1] https://dribbble.com/shots/1631956-Settings-Icons-PSD 5 | [2] https://github.com/pasqualevitiello/Tumblr-Style-Cog-Spinners 6 | 7 | -------------------------------------------------------------------------------- /ext/cogs/cogs.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: 'cogs'; 4 | src: url('./cogs.eot'); 5 | src: url('./cogs.eot?#iefix') format('embedded-opentype'), 6 | url('./cogs.woff') format('woff'), 7 | url('./cogs.ttf') format('truetype'), 8 | url('./cogs.svg#cogs') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | [class*='cog-']:before{ 13 | display: inline-block; 14 | font-family: 'cogs'; 15 | font-style: normal; 16 | font-weight: normal; 17 | line-height: 1; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-osx-font-smoothing: grayscale 20 | } 21 | 22 | .cog-01:before{content:'\0054';} 23 | .cog-02:before{content:'\0055';} 24 | .cog-03:before{content:'\0056';} 25 | .cog-04:before{content:'\0057';} 26 | .cog-05:before{content:'\0058';} 27 | .cog-06:before{content:'\0047';} 28 | .cog-07:before{content:'\0041';} 29 | .cog-08:before{content:'\0042';} 30 | .cog-09:before{content:'\0043';} 31 | .cog-10:before{content:'\0044';} 32 | .cog-11:before{content:'\0045';} 33 | .cog-12:before{content:'\0046';} 34 | .cog-13:before{content:'\0048';} 35 | .cog-14:before{content:'\0049';} 36 | .cog-15:before{content:'\004a';} 37 | .cog-16:before{content:'\004b';} 38 | .cog-17:before{content:'\004c';} 39 | .cog-18:before{content:'\004d';} 40 | .cog-19:before{content:'\004e';} 41 | .cog-20:before{content:'\004f';} 42 | .cog-21:before{content:'\0050';} 43 | .cog-22:before{content:'\0051';} 44 | .cog-23:before{content:'\0052';} 45 | .cog-24:before{content:'\0053';} 46 | -------------------------------------------------------------------------------- /ext/cogs/cogs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/ext/cogs/cogs.eot -------------------------------------------------------------------------------- /ext/cogs/cogs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/ext/cogs/cogs.ttf -------------------------------------------------------------------------------- /ext/cogs/cogs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/ext/cogs/cogs.woff -------------------------------------------------------------------------------- /ext/material/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdllc/constructr/4cbc17a9760006bc4b7f72e8e6e1e588d62d7cf5/ext/material/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /ext/material/README.md: -------------------------------------------------------------------------------- 1 | The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts: 2 | 3 | ```html 4 | 6 | ``` 7 | 8 | Read more in our full usage guide: 9 | http://google.github.io/material-design-icons/#icon-font-for-the-web 10 | -------------------------------------------------------------------------------- /ext/material/material-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Material Icons'), 6 | local('MaterialIcons-Regular'), 7 | url(MaterialIcons-Regular.woff2) format('woff2'); 8 | } 9 | 10 | .material-icons { 11 | font-family: 'Material Icons'; 12 | font-weight: normal; 13 | font-style: normal; 14 | font-size: 24px; /* Preferred icon size */ 15 | display: inline-block; 16 | width: 1em; 17 | height: 1em; 18 | line-height: 1; 19 | text-transform: none; 20 | letter-spacing: normal; 21 | word-wrap: normal; 22 | white-space: nowrap; 23 | direction: ltr; 24 | 25 | /* Support for all WebKit browsers. */ 26 | -webkit-font-smoothing: antialiased; 27 | /* Support for Safari and Chrome. */ 28 | text-rendering: optimizeLegibility; 29 | 30 | /* Support for Firefox. */ 31 | -moz-osx-font-smoothing: grayscale; 32 | 33 | /* Support for IE. */ 34 | font-feature-settings: 'liga'; 35 | } 36 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | ConstructR Shell 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /main/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | 'use strict'; 24 | 25 | if (require('electron-squirrel-startup')) return; // installer 26 | 27 | const path = require( "path" ); 28 | const electron = require('electron'); 29 | const electronwindowstate = require('electron-window-state'); 30 | 31 | const {ipcMain} = electron; 32 | const {app} = electron; 33 | const {BrowserWindow} = electron; 34 | 35 | let win; 36 | 37 | app.on('ready', function(){ 38 | 39 | let windowstate = electronwindowstate({ 40 | defaultWidth: 1000, 41 | defaultHeight: 800 42 | }); 43 | 44 | win = new BrowserWindow({ 45 | 'x': windowstate.x, 46 | 'y': windowstate.y, 47 | 'width': windowstate.width, 48 | 'height': windowstate.height, 49 | 'webPreferences': { 50 | 'experimentalCanvasFeatures': true 51 | } 52 | }); 53 | 54 | win.main_process_args = process.argv.slice(0); 55 | 56 | windowstate.manage(win); 57 | win.loadURL('file://' + __dirname + '/index.html'); 58 | win.on('closed', function() { win = null; }); 59 | 60 | }); 61 | 62 | app.on('window-all-closed', function () { 63 | if (process.platform !== 'darwin') { 64 | app.quit(); 65 | } 66 | }); 67 | 68 | app.on('activate', function () { 69 | if (win === null) { 70 | createWindow(); 71 | } 72 | }); 73 | 74 | ipcMain.on('system', function(event, arg){ 75 | if( arg === "quit" ){ 76 | app.quit(); 77 | } 78 | }); 79 | 80 | ipcMain.on('download', function(event, opts = {}){ 81 | 82 | let url = opts.url; 83 | let dest = opts.destfile; 84 | let listener = function(event, item, webContents){ 85 | 86 | let totalBytes = item.getTotalBytes(); 87 | let filePath = dest || path.join(app.getPath('downloads'), item.getFilename()); 88 | 89 | // NOTE: this fails with unix-y paths. R is building these paths incorrectly 90 | 91 | if( process.platform === "win32" ){ 92 | filePath = filePath.replace( /\//g, "\\" ); 93 | } 94 | item.setSavePath(filePath); 95 | 96 | item.on('updated', () => { 97 | win.setProgressBar(item.getReceivedBytes() / totalBytes); 98 | webContents.send( 'download-progress', { received: item.getReceivedBytes(), total: totalBytes }); 99 | }); 100 | 101 | item.on('done', (e, state) => { 102 | 103 | if (!win.isDestroyed()) { 104 | win.setProgressBar(-1); 105 | } 106 | 107 | if (state === 'interrupted') { 108 | // electron.dialog.showErrorBox('Download error', `The download of ${item.getFilename()} was interrupted`); 109 | } 110 | 111 | webContents.send( 'download-complete', { path: filePath, name: item.getFilename(), size: totalBytes, state: state }); 112 | webContents.session.removeListener('will-download', listener); 113 | 114 | }); 115 | 116 | }; 117 | 118 | win.webContents.session.on( 'will-download', listener ); 119 | win.webContents.downloadURL(url); 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "constructr", 3 | "productName": "ConstructR", 4 | "version": "0.8.3", 5 | "description": "Electron-based R shell", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/sdllc/constructr.git" 9 | }, 10 | "author": { 11 | "name": "Structured Data LLC", 12 | "email": "info@riskamp.com" 13 | }, 14 | "homepage": "https://constructr-project.com/shell", 15 | "license": "MIT", 16 | "main": "app/main.js", 17 | "scripts": { 18 | "start": "electron app/main.js", 19 | "dist": "build" 20 | }, 21 | "build": { 22 | "asar": true, 23 | "asar-unpack": "**/*.R", 24 | "asar-unpack-dir": "**/{R-3.*,node_modules/controlr,library,R}/**/*", 25 | "win": { 26 | "iconUrl": "https://raw.githubusercontent.com/sdllc/constructr/master/build/icon.ico" 27 | }, 28 | "linux": { 29 | "depends": [ 30 | "r-base-core", 31 | "r-base", 32 | "r-recommended", 33 | "libappindicator1", 34 | "libnotify-bin" 35 | ], 36 | "fpm": [ 37 | "--deb-priority", 38 | "optional", 39 | "--category", 40 | "devel" 41 | ] 42 | } 43 | }, 44 | "dependencies": { 45 | "chokidar": "^1.4.3", 46 | "cmjs-shell": "^0.3.0", 47 | "controlr": "^2.0.1", 48 | "electron-squirrel-startup": "^1.0.0", 49 | "electron-window-state": "^3.0.3", 50 | "pubsub-js": "^1.5.3", 51 | "untildify": "^3.0.2", 52 | "xml": "^1.0.1" 53 | }, 54 | "devDependencies": { 55 | "bower": "^1.7.9", 56 | "del": "^2.2.0", 57 | "electron-builder": "^3.25.0", 58 | "electron-prebuilt": "^1.2.3", 59 | "extract-zip": "^1.5.0", 60 | "gulp": "^3.9.1", 61 | "gulp-add-src": "^0.2.0", 62 | "gulp-concat": "^2.6.0", 63 | "gulp-cssnano": "^2.1.2", 64 | "gulp-htmlmin": "^2.0.0", 65 | "gulp-if": "^2.0.0", 66 | "gulp-livereload": "^3.8.1", 67 | "gulp-postcss": "^6.1.0", 68 | "gulp-rename": "^1.2.2", 69 | "gulp-run": "^1.6.12", 70 | "gulp-sourcemaps": "^1.6.0", 71 | "gulp-uglify": "^1.5.3", 72 | "gulp-util": "^3.0.7", 73 | "lodash.assign": "^4.0.7", 74 | "postcss-import": "^8.1.0", 75 | "request": "^2.72.0", 76 | "vinyl-buffer": "^1.0.0", 77 | "vinyl-source-stream": "^1.1.0", 78 | "webpack": "^1.12.14", 79 | "webpack-stream": "^3.1.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | Packages 2 | ======== 3 | 4 | Package API in development. Packages are intended to split 5 | core and non-core functionality, but they also replace the 6 | plugin system so there's a single unified interface for 7 | default and optional packages. 8 | 9 | Packages are like libraries but with some application- 10 | and runtime-specific functionality 11 | 12 | Packages *may* (but don't have to) export an `init(core)` 13 | method, returning a promise. This will be called with a parameter 14 | containing useful objects -- R, Settings, Settings, Hooks, 15 | Constants, Messages, Utilities, (...) 16 | 17 | Dependencies 18 | ------------ 19 | 20 | Dependendies are supported using a `packageDepdendencies` array 21 | (or object, for versioning). The package won't be initialized 22 | until all depdendencies are available, but it will be loaded; 23 | so don't expect dependencies to be available before the `init()` 24 | method is called. 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/browser/browser.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 90 | 91 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /packages/browser/browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | var path = require("path"); 26 | var PubSub = require( "pubsub-js" ); 27 | 28 | /** inject stylesheet into help pages */ 29 | var help_stylesheet = null; 30 | 31 | module.exports = { 32 | 33 | init: function(core){ 34 | 35 | var html = path.join( "packages", "browser", "browser.html" ); 36 | core.Utils.install_html_component( html ); 37 | 38 | core.Hooks.install( "browser", function(hook, url){ 39 | 40 | if( core.Settings["browser.panel"]) 41 | { 42 | var browser = document.getElementById( "browser-pane" ); 43 | if( !browser ){ 44 | 45 | browser = document.createElement( "browser-component" ); 46 | browser.id = "browser-pane"; 47 | browser.cache = core.Utils.file_cache; 48 | 49 | // don't destroy if the panel closes 50 | browser.setAttribute( "data-preserve", true ); 51 | 52 | // close box 53 | browser.addEventListener( "close", function(e){ 54 | //sidePanel.pop(); 55 | PubSub.publish( "side-panel-pop", browser ); 56 | }); 57 | 58 | // on load, possibly inject stylesheet 59 | browser.addEventListener( 'load-commit', function(e){ 60 | var url = e.url || e.detail.url; 61 | if( url && url.match( /^http\:\/\/127\.0\.0\.1\:\d+\/(?:library|doc)\// )){ 62 | if( help_stylesheet && help_stylesheet !== "default" ){ 63 | var ss = path.join( help_styles.dir, help_stylesheet + ".css" ); 64 | browser.inject_stylesheet( ss ); 65 | } 66 | } 67 | }); 68 | 69 | } 70 | 71 | /** 72 | * this must be done _after_ the parent has been changed 73 | * or it will crash hard (this is a bug in electron or polymer?) 74 | */ 75 | browser._onShow = function(){ 76 | browser.open( url ); 77 | }; 78 | 79 | PubSub.publish( "side-panel-attach", { node: browser }); 80 | return true; 81 | 82 | } 83 | return false; 84 | 85 | }); 86 | 87 | return Promise.resolve(); 88 | } 89 | 90 | }; -------------------------------------------------------------------------------- /packages/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser", 3 | "version": "0.1.0", 4 | "main": "browser.js", 5 | "packageDependencies": [ 6 | "side-panel" 7 | ], 8 | "preferences": { 9 | "browser.panel": { 10 | "type": "boolean", 11 | "default": false, 12 | "label": "Use browser panel" 13 | } 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /packages/cran/cran.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const path = require( "path" ); 26 | const PubSub = require( "pubsub-js" ); 27 | const electron = require('electron'); 28 | 29 | /** 30 | * open the CRAN mirror selector in the panel 31 | * 32 | * TODO: make this modal? 33 | */ 34 | var open_mirror_chooser = function(core, cb){ 35 | 36 | console.info( "OMC CB", cb, "t?", typeof cb ); 37 | 38 | var panel = document.createElement( "mirror-chooser" ); 39 | panel.id = "mirror-chooser-panel"; 40 | panel.className = "panel"; 41 | panel.addEventListener( 'close', function(){ 42 | PubSub.publish( core.Constants.SIDE_PANEL_POP, panel ); 43 | }); 44 | panel.$.cancel.addEventListener( 'click', function(){ 45 | PubSub.publish( core.Constants.SIDE_PANEL_POP, panel ); 46 | }); 47 | 48 | panel.message = core.Messages.LOADING_MIRROR_LIST; 49 | 50 | PubSub.publish( core.Constants.SIDE_PANEL_PUSH, { node: panel }); 51 | 52 | var mirror = undefined; 53 | var selected_index = 0; 54 | 55 | core.R.get_cran_mirror().then( function( m ){ 56 | if( m && m !== "@CRAN@" ){ 57 | mirror = m; 58 | panel.http = ( !mirror.startsWith( "https" )); 59 | } 60 | else panel.http = false; 61 | return core.Utils.data_cache.get_cached_data( "cran.mirrors", core.Utils.data_cache.DEFAULT_CACHE_TIME, "getCRANmirrors()" ); 62 | }).then( function( obj ){ 63 | 64 | if( obj && obj.$data ){ 65 | var data = core.Utils.data_cache.transpose_frame( obj.$data ); 66 | 67 | // check the current mirror, independent of filter 68 | 69 | // it seems like chooseCRANmirror sets the mirror WITHOUT 70 | // a trailing slash, even though the list entries have a 71 | // trailing slash. So. 72 | 73 | for( var i = 0; i< data.length; i++ ){ 74 | data[i].checked = false; 75 | data[i].index = i; 76 | if( mirror && ( mirror === data[i].URL || mirror + "/" === data[i].URL )){ 77 | data[i].checked = true; 78 | data[i].initial_selection = true; 79 | } 80 | } 81 | 82 | var filter_http = function(http){ 83 | var filtered = data.filter(function(elt){ 84 | return ( http || elt.URL.startsWith( "https" )); 85 | }); 86 | selected_index = 0; 87 | for( var i = 0; i< filtered.length; i++ ){ 88 | if( filtered[i].checked ){ 89 | selected_index = i; 90 | break; 91 | } 92 | } 93 | return filtered; 94 | } 95 | 96 | panel.addEventListener( 'filter-change', function(){ 97 | panel.data = filter_http(panel.http); 98 | panel.scrollTo(selected_index); 99 | }); 100 | 101 | panel.addEventListener( 'selection-change', function(e){ 102 | var index = e.detail.index; 103 | for( var i = 0; i< data.length; i++ ){ 104 | if(i === index){ 105 | data[i].checked = true; 106 | mirror = data[i].URL; 107 | } 108 | else data[i].checked = false; 109 | } 110 | panel.data = filter_http(panel.http); 111 | }); 112 | 113 | panel.$.accept.addEventListener( 'click', function(e){ 114 | 115 | e.stopPropagation(); 116 | e.preventDefault(); 117 | 118 | if( typeof mirror === "undefined" ) return; 119 | 120 | if( panel.setDefault ){ 121 | console.info( "setting default mirror" ); 122 | core.Settings["default.mirror"] = mirror ; 123 | } 124 | 125 | var msg = `${core.Messages.SET_CRAN_MIRROR}: ${mirror}`; 126 | 127 | core.R.set_cran_mirror( mirror, msg ).then( function(){ 128 | if( cb ) cb.call(this); 129 | else PubSub.publish( core.Constants.SIDE_PANEL_POP, panel); 130 | PubSub.publish( core.Constants.SHELL_FOCUS ); 131 | //shell.focus(); 132 | }).catch( function(){ 133 | if( cb ) cb.call(this); 134 | else PubSub.publish( core.Constants.SIDE_PANEL_POP, panel); 135 | PubSub.publish( core.Constants.SHELL_FOCUS ); 136 | //shell.focus(); 137 | }); 138 | 139 | }); 140 | 141 | panel.data = filter_http(panel.http); 142 | panel.scrollTo(selected_index); 143 | } 144 | else { 145 | panel.message = core.Messages.ERROR_OCCURRED; 146 | } 147 | 148 | }); 149 | 150 | }; 151 | 152 | 153 | /** 154 | * if a mirror is set, open the package chooser. otherwise open the 155 | * mirror chooser, and then (on accept) open the package chooser. 156 | */ 157 | var open_package_chooser_if = function(core){ 158 | 159 | core.R.get_cran_mirror().then( function( m ){ 160 | if( m && !m.startsWith('@')){ 161 | open_package_chooser(core); 162 | } 163 | else { 164 | PubSub.publish(core.Constants.SHELL_MESSAGE, [ core.Messages.PLEASE_SELECT_MIRROR, "shell-system-information"]); 165 | open_mirror_chooser( core, open_package_chooser.bind(this, core)); 166 | } 167 | }).catch( function(e){ 168 | console.info(e); 169 | }); 170 | 171 | }; 172 | 173 | var open_package_chooser = function(core){ 174 | 175 | var panel = document.createElement( "package-chooser" ); 176 | panel.id = "package-chooser-panel"; 177 | panel.className = "panel"; 178 | panel.addEventListener( 'close', function(){ PubSub.publish( core.Constants.SIDE_PANEL_POP, panel ); }); 179 | panel.$.cancel.addEventListener( 'click', function(){ PubSub.publish( core.Constants.SIDE_PANEL_POP, panel ); }); 180 | 181 | let mirror; 182 | let click_cran_link = function(e){ 183 | electron.shell.openExternal(e.detail.href); 184 | } 185 | panel.addEventListener( "click-link", click_cran_link ); 186 | 187 | panel.message = core.Messages.LOADING_PACKAGE_LIST; 188 | 189 | PubSub.publish( core.Constants.SIDE_PANEL_PUSH, { node: panel }); 190 | 191 | var packages = undefined; 192 | var installed = undefined; 193 | 194 | core.R.get_cran_mirror().then( function( rslt ){ 195 | mirror = rslt ; 196 | if( !mirror.endsWith( "/" )) mirror = mirror + "/"; 197 | return core.Utils.data_cache.get_cached_data( "available.packages", core.Utils.data_cache.DEFAULT_CACHE_TIME, "available.packages()" ); 198 | 199 | }).then( function( obj ){ 200 | 201 | packages = core.Utils.data_cache.build_matrix(obj.$data, obj.$nrows, obj.$ncols, true ); 202 | 203 | // this is nice, but unecessary and expensive 204 | packages = core.Utils.data_cache.apply_names( packages, 1, obj.$dimnames.$data[1] ); 205 | return core.R.queued_internal( "installed.packages()" ); 206 | 207 | }).then( function( obj ){ 208 | 209 | // leave this one as column-dominant; we only need the first column 210 | installed = core.Utils.data_cache.build_matrix( obj.response.$data, obj.response.$nrows, obj.response.$ncols ); 211 | var installed_packages = installed[0]; 212 | 213 | // why not walk both arrays at once? there's a possibility 214 | // that something is installed but not from CRAN; we might 215 | // get stuck on that (FIXME: just use lexical comparison) 216 | 217 | for( var i = 0; i< packages.length; i++ ){ 218 | 219 | // using property bindings we need these to be !undef 220 | packages[i].installed = false; 221 | packages[i].checked = false; 222 | 223 | // for ref 224 | packages[i].index = i; 225 | packages[i].link = mirror + "web/packages/" + packages[i].Package; 226 | 227 | for( var j = 0; j< installed_packages.length; j++ ){ 228 | if( packages[i].Package === installed_packages[j] ){ 229 | packages[i].installed = true; 230 | packages[i].Package += ( " " + core.Messages.INSTALLED_FLAG ); 231 | break; 232 | } 233 | } 234 | } 235 | 236 | // console.info( packages, installed ); 237 | panel.data = packages; 238 | panel.$.accept.addEventListener( 'click', function(){ 239 | 240 | var list = []; 241 | for( var i = 0; i< packages.length; i++ ){ 242 | if( packages[i].checked ){ 243 | console.info( packages[i] ); 244 | list.push( `"${packages[i].Package}"`); 245 | } 246 | } 247 | 248 | PubSub.publish( core.Constants.SIDE_PANEL_POP, panel ); 249 | setImmediate(function(){ 250 | if( list.length ){ 251 | //shell.block(); 252 | PubSub.publish(core.Constants.SHELL_BLOCK); 253 | PubSub.publish(core.Constants.SHELL_MESSAGE, [core.Messages.INSTALLING_PACKAGES, "shell-system-information" ]); 254 | var cmd = `install.packages(c(${list.join(',')}))`; 255 | core.R.queued_exec( cmd ).then( function( packet ){ 256 | //shell.unblock.apply( this, arguments ); 257 | PubSub.publish( core.Constants.SHELL_UNBLOCK, arguments ); 258 | PubSub.publish( core.Constants.SHELL_FOCUS ); 259 | }) 260 | } 261 | }); 262 | }); 263 | 264 | }).catch(function(e){ 265 | panel.message = core.Messages.ERROR_OCCURRED; 266 | console.info(e); 267 | }); 268 | 269 | } 270 | 271 | 272 | module.exports = { 273 | 274 | init: function(core){ 275 | 276 | var html = path.join( "packages", "cran", "cran.html" ); 277 | core.Utils.install_html_component( html ); 278 | 279 | // menu 280 | PubSub.subscribe( "menu-click", function(){ 281 | var data = arguments[1]; 282 | if( data.message === "choose-mirror" ){ 283 | open_mirror_chooser( core ); 284 | } 285 | if( data.message === "install-packages" ){ 286 | open_package_chooser_if( core ); 287 | } 288 | }); 289 | 290 | // direct 291 | PubSub.subscribe( "open-mirror-chooser", function(cb){ 292 | open_mirror_chooser( core, cb ); 293 | }); 294 | 295 | // system (shoudl be hook) 296 | core.R.on('system', function(msg){ 297 | var cmd = msg.$data ? msg.$data.cmd : undefined; 298 | if( cmd === "chooseCRANmirror" ){ 299 | open_mirror_chooser( core ); 300 | } 301 | if( cmd === "install.packages" ){ 302 | open_package_chooser_if( core ); 303 | } 304 | }); 305 | 306 | } 307 | }; 308 | -------------------------------------------------------------------------------- /packages/cran/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cran", 3 | "version": "0.1.0", 4 | "main": "cran.js", 5 | "packageDependencies": [ 6 | "side-panel" 7 | ] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/download/download.R: -------------------------------------------------------------------------------- 1 | 2 | #------------------------------------------------------------------------------ 3 | # download: add handler for download method "js", which uses built-in 4 | # support in electron 5 | #------------------------------------------------------------------------------ 6 | 7 | (function(){ 8 | 9 | override.binding <- function( name, func, ns, assign.in.namespace=T ){ 10 | if( exists( name ) ){ 11 | package <- paste0( "package:", ns ); 12 | unlockBinding( name, as.environment(package)); 13 | assign( name, func, as.environment(package)); 14 | if( assign.in.namespace ){ 15 | ns <- asNamespace( ns ); 16 | if (bindingIsLocked(name, ns)) { 17 | unlockBinding(name, ns) 18 | assign(name, func, envir = ns, inherits = FALSE) 19 | w <- options("warn") 20 | on.exit(options(w)) 21 | options(warn = -1) 22 | lockBinding(name, ns) 23 | } 24 | else assign(name, func, envir = ns, inherits = FALSE); 25 | } 26 | lockBinding( name, as.environment(package)); 27 | } 28 | } 29 | 30 | download.file.original <- get( "download.file", envir=as.environment( "package:utils" )); 31 | override.binding( "download.file", 32 | function (url, destfile, method, quiet = FALSE, mode = "w", cacheOK = TRUE, 33 | extra = getOption("download.file.extra")) 34 | { 35 | # arglist <- as.list( match.call())[-1]; 36 | # cat( paste( "DL", url, destfile, "\n" )); 37 | 38 | method <- if (missing(method)) 39 | getOption("download.file.method", default = "auto") 40 | else match.arg(method, c("auto", "internal", "wininet", "libcurl", 41 | "wget", "curl", "lynx", "js")) 42 | 43 | if( method == "js" ){ 44 | jsClientLib:::.js.client.callback.sync( list( arguments=as.list(environment()), command="download" )); 45 | } 46 | else { 47 | # do.call( download.file.original, arglist, envir=parent.env(environment())); 48 | do.call( download.file.original, as.list(environment()), envir=parent.env(environment())); 49 | } 50 | }, "utils", T ); 51 | 52 | options( download.file.method="js" ); 53 | 54 | })(); 55 | 56 | -------------------------------------------------------------------------------- /packages/download/download.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const PubSub = require( "pubsub-js" ); 26 | const ipcRenderer = require('electron').ipcRenderer; 27 | 28 | /** 29 | * download a file, using electron utilities and 30 | * (optionally) adding a progress bar. 31 | */ 32 | const download_file = function(core, opts){ 33 | 34 | return new Promise( function( resolve, reject ){ 35 | 36 | let progressbar = null; 37 | 38 | if( !opts.quiet ){ 39 | PubSub.publish( core.Constants.SHELL_MESSAGE, [ `\n${core.Messages.TRYING_URL}: ${opts.url}\n` ]); 40 | progressbar = core.Packages.progress.createProgressBar({ 41 | label: function(p){ 42 | return ( p >= 100 ) ? core.Messages.DOWNLOAD_COMPLETE : `${core.Messages.DOWNLOADING}: ${p}%`; 43 | }, 44 | min: 0, max: 1, value: 0, width: 30 45 | }); 46 | PubSub.publish( core.Constants.SHELL_INSERT_NODE, [progressbar.node, true]); 47 | } 48 | 49 | ipcRenderer.on( 'download-progress', function( event, args ){ 50 | if( progressbar ){ 51 | if( progressbar.max() === 1 ) progressbar.max( args.total ); 52 | progressbar.value( args.received ); 53 | } 54 | }); 55 | 56 | ipcRenderer.on( 'download-complete', function( event, args ){ 57 | 58 | if( args.state !== "completed" ){ 59 | PubSub.publish(Constants.SHELL_MESSAGE, [ 60 | `\n${core.Messages.DOWNLOAD_FAILED}: ${args.state}\n` 61 | ]); 62 | } 63 | 64 | ipcRenderer.removeAllListeners( "download-complete" ); 65 | ipcRenderer.removeAllListeners( "download-progress" ); 66 | 67 | resolve( args.state === "completed" ? 0 : -1 ); 68 | 69 | }); 70 | 71 | ipcRenderer.send( "download", opts ); 72 | 73 | }); 74 | 75 | }; 76 | 77 | module.exports = { 78 | init: function(core){ 79 | core.Hooks.install( "sync-request-p", function(hook, req){ 80 | let cmd = req.command ? req.command : req.$data ? req.$data.command : null; 81 | if( cmd === "download" ){ 82 | return download_file( core, req.$data.arguments.$data ); 83 | } 84 | return null; 85 | }); 86 | } 87 | }; 88 | 89 | -------------------------------------------------------------------------------- /packages/download/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "download", 3 | "version": "0.1.0", 4 | "main": "download.js", 5 | "R": "download.R", 6 | "packageDependencies": [ 7 | "progress" 8 | ] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /packages/file-watcher/file-watcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const fs = require("fs"); 26 | const PubSub = require( "pubsub-js" ); 27 | const untildify = require( "untildify" ); 28 | const Chokidar = require( "chokidar" ); 29 | 30 | const DEBOUNCE_TIMEOUT = 250; 31 | 32 | const debounce_id = {}; 33 | 34 | let listener = function( change_callback, original_path, filename ){ 35 | 36 | // here's what happens on windows: there are two events fired on 37 | // file change. generally the file will be locked on the first 38 | // event, and unlocked on the second event. using a debounce 39 | // timer should ensure we get the second event and the file will 40 | // be unlocked. 41 | 42 | // I don't know what the expected or average wait is between events 43 | // 1 and 2. an alternative would be to check for locks (how? just 44 | // try to open for reading). also we can probably skip all this on 45 | // linux/osx. 46 | 47 | if( debounce_id[original_path] ) clearTimeout( debounce_id[original_path] ); 48 | debounce_id[original_path] = setTimeout( function(){ 49 | change_callback.call( null, filename, original_path ); 50 | debounce_id[original_path] = 0; 51 | }, DEBOUNCE_TIMEOUT ); 52 | 53 | }; 54 | 55 | var internal_watches = {}; 56 | 57 | var FileWatcher = function(){ 58 | 59 | var watches = {}; 60 | var changes = {}; 61 | var instance = this; 62 | 63 | this.init = function(opts){ 64 | 65 | if( opts.source ){ 66 | 67 | opts.source.on( 'file.watch', function(data){ 68 | //console.info( data ); 69 | if( data.$data.command === "watch" ){ 70 | //console.info( "watch", data.$data.path ); 71 | data.$data.path = untildify( data.$data.path ); 72 | 73 | if( watches[data.$data.path] ) watches[data.$data.path].close(); 74 | watches[data.$data.path] = Chokidar.watch( data.$data.path ); 75 | if( opts.change_callback ) 76 | watches[data.$data.path].on( 'change', listener.bind( this, opts.change_callback, data.$data.path )); 77 | } 78 | else if( data.$data.command === "unwatch" ){ 79 | if( watches[data.$data.path] ){ 80 | watches[data.$data.path].close(); 81 | delete( watches[data.$data.path] ); 82 | } 83 | } 84 | else if( data.$data.command === "clear" ){ 85 | Object.keys( watches ).map( function( key ){ 86 | watches[key].close(); 87 | }) 88 | watches = {}; 89 | } 90 | else if( data.$data.command === "reloading" ){ 91 | if( opts.notify_callback ){ 92 | var msg; 93 | if( data.$data.filename === data.$data.original_path ) 94 | msg = `FileWatcher reloading ${data.$data.filename}\n` ; 95 | else msg = `FileWatcher reloading ${data.$data.filename} (${data.$data.original_path})\n` ; 96 | if( opts.notify_callback ) opts.notify_callback.call( this, msg ); 97 | } 98 | } 99 | }); 100 | } 101 | 102 | }; 103 | 104 | } 105 | 106 | module.exports = { 107 | 108 | init: function( core ){ 109 | new FileWatcher().init({ 110 | source: core.R, 111 | change_callback: function( path, original_path ){ 112 | //console.info( `filewatcher cb: ${path} (${original_path})`); 113 | var cmd = `.js.client.file.changed('${core.Utils.escape_backslashes(path, 2)}', '${core.Utils.escape_backslashes(original_path, 2)}')`; 114 | //console.info( cmd ); 115 | core.R.queued_internal( cmd ); // FIXME: should use exec? 116 | }, 117 | notify_callback: function(msg){ 118 | PubSub.publish( core.Constants.SHELL_MESSAGE, [ msg, "shell-system-information" ]); 119 | } 120 | }); 121 | return Promise.resolve(); 122 | }, 123 | 124 | /** 125 | * we have a separate file watcher for internal operations 126 | */ 127 | watch_internal: function(opts){ 128 | opts.path = untildify( opts.path ); 129 | internal_watches[ opts.path ] = Chokidar.watch( opts.path ); 130 | internal_watches[ opts.path ].on( 'change', listener.bind( null, opts.change, opts.path )); 131 | }, 132 | 133 | unwatch_internal: function(opts){ 134 | opts.path = untildify( opts.path ); 135 | internal_watches[ opts.path ].close(); 136 | delete internal_watches[ opts.path ]; 137 | } 138 | 139 | }; 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /packages/file-watcher/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-watcher", 3 | "version": "0.1.0", 4 | "main": "file-watcher.js" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/graphics/graphics-panel.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 59 | 60 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /packages/graphics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphics", 3 | "version": "0.1.1", 4 | "main": "graphics-device.js", 5 | "htmlComponents": [ 6 | "graphics-panel.html" 7 | ], 8 | "preferences": { 9 | "save.graphics.data": { 10 | "type": "boolean", 11 | "default": true, 12 | "label": "Save graphics data" 13 | } 14 | }, 15 | "preferenceGroups": { 16 | "Graphics Target": { 17 | "graphics.target": { 18 | "type": "choice", 19 | "options": "get_graphics_targets", 20 | "label": "", 21 | "default": "inline" 22 | } 23 | }, 24 | "Inline Graphics Size": { 25 | "inline.graphics.size": { 26 | "type": "size", 27 | "default": { "width": 600, "height": 400 }, 28 | "min": { "width": 50, "height": 50 } 29 | } 30 | }, 31 | "Graphics Font Mapping": { 32 | "font.map.sans": { 33 | "type": "input", 34 | "placeholder": "sans-serif", 35 | "default.win32": "Segoe UI", 36 | "default.darwin": "Helvetica Neue" 37 | }, 38 | "font.map.serif": { 39 | "type": "input", 40 | "placeholder": "serif", 41 | "default.win32": "Palatino", 42 | "default.darwin": "Georgia" 43 | }, 44 | "font.map.mono": { 45 | "type": "input", 46 | "placeholder": "monospace", 47 | "default.win32": "Consolas", 48 | "default.darwin": "Menlo" 49 | } 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /packages/histogram-view/histogram-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const PubSub = require( "pubsub-js" ); 26 | 27 | const electron = require('electron'); 28 | const remote = electron.remote; 29 | const Menu = remote.Menu; 30 | const MenuItem = remote.MenuItem; 31 | 32 | var path = require( "path" ); 33 | 34 | const createInstance = function(core, opts, src){ 35 | 36 | let node = document.createElement("histogram-panel"); 37 | node.data = src === "locals" ? opts.data.$data.histogram : opts.histogram; 38 | 39 | let token = 0; 40 | node.field = opts.name; 41 | 42 | let onclose = function(){ 43 | PubSub.publish( core.Constants.SIDE_PANEL_REMOVE, node ); 44 | }; 45 | 46 | node.addEventListener( "close", onclose ); 47 | node.onShow = function(){ 48 | 49 | if( src === "locals" ){ 50 | let name = opts.name; 51 | token = PubSub.subscribe( "locals", function(ch, locals){ 52 | if( !locals.$data.fields.$data[name] 53 | || !locals.$data.fields.$data[name].$data 54 | || !locals.$data.fields.$data[name].$data.histogram ){ 55 | node.data = null; 56 | return; 57 | } 58 | node.data = locals.$data.fields.$data[name].$data.histogram; 59 | }); 60 | } 61 | else if( src === "watches" ){ 62 | token = PubSub.subscribe( "watch", function(ch, watches){ 63 | let key = opts.key; 64 | for( let i = 0; i< watches.length; i++ ){ 65 | if( key === watches[i].key ){ 66 | node.data = watches[i].histogram; 67 | return; 68 | } 69 | } 70 | node.data = null; 71 | }); 72 | } 73 | }; 74 | 75 | node.onHide = function(){ 76 | if( token ){ 77 | PubSub.unsubscribe( token ); 78 | token = 0; 79 | } 80 | }; 81 | 82 | node.onUnload = function(){ 83 | if( token ){ 84 | PubSub.unsubscribe( token ); 85 | token = 0; 86 | } 87 | node.removeEventListener( "close", onclose ); 88 | }; 89 | 90 | return node; 91 | 92 | }; 93 | 94 | module.exports = { 95 | 96 | init: function(core){ 97 | 98 | core.Hooks.install( "locals_click", function( hook, opts ){ 99 | // ... 100 | return false; 101 | }); 102 | 103 | const show = function(node){ 104 | let pos = core.Settings["histogram.panel.position"]; 105 | if( !pos ) pos = core.Settings["details.panel.position"]; 106 | if( !pos ) pos = { row: 3, column: 0 }; 107 | PubSub.publish( core.Constants.SIDE_PANEL_ATTACH, { position: pos, node: node }); 108 | }; 109 | 110 | let menuitem = new MenuItem({ 111 | label: "View histogram", 112 | click: function( menuitem ){ 113 | show(createInstance( core, menuitem.menu_target, menuitem.menu_source )); 114 | } 115 | }); 116 | 117 | core.Hooks.install( "locals_context_menu", function( hook, menu ){ 118 | menuitem.menu_target = menu.target; 119 | menuitem.menu_source = "locals"; 120 | menu.insert( 3, menuitem ); 121 | menuitem.visible = !!menu.target.data.$data.histogram; 122 | }); 123 | 124 | core.Hooks.install( "watches_context_menu", function( hook, menu ){ 125 | menuitem.menu_target = menu.target; 126 | menuitem.menu_source = "watches"; 127 | menu.insert( 3, menuitem ); 128 | menuitem.visible = !!menu.target.histogram; 129 | }); 130 | 131 | // (optionally) override default click on locals 132 | core.Hooks.install( "locals_click", function( hook, opts ){ 133 | 134 | if( !core.Utils.array_cross_match( core.Settings["locals.default.view"], "histogram" ) 135 | || !opts.data.$data.histogram ) return false; 136 | 137 | // we have to be well-behaved 138 | if( opts.handled ) return false; 139 | opts.handled = true; 140 | 141 | show( createInstance( core, opts, "locals" )); 142 | 143 | return true; 144 | }); 145 | 146 | core.Hooks.install( "watches_click", function( hook, opts ){ 147 | 148 | if( !core.Utils.array_cross_match( core.Settings["watches.default.view"], "histogram" ) 149 | || !opts.histogram ) return false; 150 | 151 | // we have to be well-behaved 152 | if( opts.handled ) return false; 153 | opts.handled = true; 154 | 155 | show( createInstance( core, opts, "watches" )); 156 | 157 | return true; 158 | }); 159 | 160 | } 161 | 162 | }; -------------------------------------------------------------------------------- /packages/histogram-view/histogram.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 67 | 76 | 77 | 78 | 79 | 105 | 166 | 167 | 168 | 169 | 180 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /packages/histogram-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "histogram-view", 3 | "version": "0.1.0", 4 | "main": "histogram-view.js", 5 | "packageDependencies": [ 6 | "side-panel" 7 | ], 8 | "htmlComponents": [ 9 | "histogram.html" 10 | ] 11 | } 12 | 13 | -------------------------------------------------------------------------------- /packages/html-dialog/dialog.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 124 | 125 | 192 | -------------------------------------------------------------------------------- /packages/html-dialog/html-dialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const path = require( "path" ); 26 | const PubSub = require( "pubsub-js" ); 27 | 28 | /** 29 | * show a dialog and return result/cancel via promise 30 | * 31 | * fields title, cancel-button, cancel-text, accept-button, 32 | * accept-text are passed directly to dialog (there are defaults). 33 | * 34 | * validate() is a function that is called on accept events, 35 | * if validate returns false it prevents closing the dialog. 36 | * 37 | * content is added to the dialog, this should be an html node. 38 | * content will get removed the next time this function is called, 39 | * so manage that node if you need to. 40 | */ 41 | function show_dialog(core, opts){ 42 | 43 | opts = opts || {}; 44 | 45 | var on_cancel = function(){ 46 | dialog.removeEventListener( "cancel", on_cancel ); 47 | dialog.removeEventListener( "accept", on_accept ); 48 | dialog.show(false); 49 | setImmediate( function(){ 50 | if( opts.complete ) opts.complete(false); 51 | }); 52 | }; 53 | 54 | var on_accept = function(){ 55 | if( opts.validate && !opts.validate.call(dialog)) return; 56 | dialog.removeEventListener( "cancel", on_cancel ); 57 | dialog.removeEventListener( "accept", on_accept ); 58 | dialog.show(false); 59 | setImmediate( function(){ 60 | if( opts.complete ) opts.complete(true); 61 | }); 62 | }; 63 | 64 | var dialog = document.getElementById( "dialog" ); 65 | if( !dialog ){ 66 | dialog = document.createElement( "html-dialog" ); 67 | dialog.id = "dialog"; 68 | dialog.addEventListener( "cancel", function(e){ 69 | dialog.show(false); 70 | }); 71 | dialog.show(false); 72 | document.body.appendChild( dialog ); 73 | } 74 | 75 | dialog.addEventListener( "cancel", on_cancel ); 76 | dialog.addEventListener( "accept", on_accept ); 77 | 78 | // content 79 | 80 | dialog.clear(); 81 | if( opts.content ) dialog.appendChild( opts.content ); 82 | 83 | // set fields or defaults 84 | 85 | ['cancel-button', 'accept-button'].map( function( a ){ 86 | if( typeof opts[a] !== "undefined" ) dialog[a] = opts[a]; 87 | else dialog[a] = true; 88 | }) 89 | 90 | dialog['cancel-text'] = opts['cancel-text'] || "Cancel"; 91 | dialog['accept-text'] = opts['accept-text'] || "Accept"; 92 | 93 | dialog.title = opts.title || "Dialog"; 94 | 95 | // ok, show 96 | 97 | dialog.show(); 98 | 99 | } 100 | 101 | module.exports = { 102 | 103 | init: function(core){ 104 | 105 | let html = path.join( "packages", "html-dialog", "dialog.html" ); 106 | core.Utils.install_html_component( html ); 107 | 108 | Object.assign( core.Constants, { 109 | DIALOG_SHOW: "dialog-show" 110 | }); 111 | 112 | PubSub.subscribe( core.Constants.DIALOG_SHOW, function( channel, opts ){ 113 | show_dialog( core, opts ); 114 | }); 115 | 116 | } 117 | 118 | }; -------------------------------------------------------------------------------- /packages/html-dialog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-dialog", 3 | "version": "0.1.0", 4 | "main": "html-dialog.js" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/pager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pager", 3 | "version": "0.1.0", 4 | "main": "pager.js" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/pager/pager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const fs = require( "fs" ); 26 | const PubSub = require( "pubsub-js" ); 27 | 28 | var Pager = function(){ 29 | 30 | this.init = function(opts){ 31 | 32 | opts.source.on( 'pager', function(obj){ 33 | 34 | var data = obj.$data; 35 | 36 | // can be one or many? use the loop 37 | 38 | if( typeof( data.files ) === "string" ){ 39 | data.files = [data.files]; 40 | data.title = [data.title]; 41 | data['delete.file'] = [data['delete.file']]; 42 | } 43 | 44 | if( opts.debug ) console.info( data ); 45 | 46 | if( typeof( data.files ) === "object" ){ 47 | for( var i = 0; i< data.files.length; i++ ){ 48 | 49 | var title = null; 50 | if( data.title[i] && data.title[i].trim().length ){ 51 | title = "\n" + data.title[i] + "\n" + Array( data.title[i].length + 1 ).join('=') + "\n"; 52 | } 53 | 54 | if( data.files[i] ){ 55 | fs.readFile( data.files[i], { encoding: "utf8" }, function(err, contents){ 56 | 57 | if( title ) opts.text( title, "pager pager-title", true ); 58 | 59 | if( contents && contents.length ){ 60 | opts.text( "\n" + contents, "pager pager-text", true ); 61 | } 62 | 63 | opts.text( "\n", undefined, true ); 64 | 65 | if( data['delete.file'][i] ){ 66 | console.info( "unlink", data.files[i] ); 67 | fs.unlink( data.files[i] ); 68 | } 69 | }); 70 | } 71 | 72 | } 73 | } 74 | 75 | }); 76 | }; 77 | 78 | }; 79 | 80 | module.exports = { 81 | 82 | init: function( core ){ 83 | new Pager().init({ 84 | source: core.R, 85 | text: function(){ 86 | var args = []; 87 | for( var i = 0; i< arguments.length; i++ ) args[i] = arguments[i]; 88 | PubSub.publish( core.Constants.SHELL_MESSAGE, args ); 89 | } 90 | }); 91 | return Promise.resolve(); 92 | } 93 | 94 | }; 95 | -------------------------------------------------------------------------------- /packages/progress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "progress", 3 | "version": "0.1.0", 4 | "main": "progress.js", 5 | "R": "progress.R" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /packages/progress/progress.R: -------------------------------------------------------------------------------- 1 | 2 | (function(){ 3 | 4 | #----------------------------------------------------------------------------- 5 | # replace win progress bar with an inline progress bar. there's a slight 6 | # difference in that these functions might not exist on linux. 7 | #----------------------------------------------------------------------------- 8 | 9 | override.binding <- function( name, func, ns, assign.in.namespace=T ){ 10 | if( exists( name ) ){ 11 | package <- paste0( "package:", ns ); 12 | unlockBinding( name, as.environment(package)); 13 | assign( name, func, as.environment(package)); 14 | if( assign.in.namespace ){ 15 | ns <- asNamespace( ns ); 16 | if (bindingIsLocked(name, ns)) { 17 | unlockBinding(name, ns) 18 | assign(name, func, envir = ns, inherits = FALSE) 19 | w <- options("warn") 20 | on.exit(options(w)) 21 | options(warn = -1) 22 | lockBinding(name, ns) 23 | } 24 | else assign(name, func, envir = ns, inherits = FALSE); 25 | } 26 | lockBinding( name, as.environment(package)); 27 | } 28 | } 29 | 30 | override.binding( "winProgressBar", js.client.progress.bar, "utils"); 31 | override.binding( "setWinProgressBar", js.client.set.progress.bar, "utils"); 32 | override.binding( "getWinProgressBar", js.client.get.progress.bar, "utils"); 33 | 34 | # optionally this one as well 35 | 36 | #override.binding( "txtProgressBar", js.client.progress.bar, "utils"); 37 | #override.binding( "setTxtProgressBar", js.client.set.progress.bar, "utils"); 38 | #override.binding( "getTxtProgressBar", js.client.get.progress.bar, "utils"); 39 | 40 | })(); 41 | -------------------------------------------------------------------------------- /packages/progress/progress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | const PubSub = require( "pubsub-js" ); 26 | 27 | /** 28 | * progress bar(s) 29 | * 30 | * reimplemented as a singleton, which handles its own events. 31 | * we need to bind an event source potentially after construction, 32 | * so there's an init() method. 33 | */ 34 | var ProgressBar = function(opts){ 35 | 36 | opts = opts || { min: 0, max: 1, initial: 0, label: undefined }; 37 | if( typeof opts.value === "undefined" ) opts.value = opts.initial || 0; 38 | opts.range = opts.max - opts.min; 39 | 40 | this.render = function(){ 41 | var p = Math.round( 100 * ( opts.value - opts.min ) / opts.range ); 42 | progress.setAttribute( 'style', `width: ${p}%` ); 43 | if( opts.label ){ 44 | if( typeof opts.label === "function" ){ 45 | label.textContent = opts.label.call( this, p ); 46 | } 47 | else { 48 | label.textContent = `${opts.label}: ${p}%`; 49 | } 50 | } 51 | else label.textContent = `${p}%`; 52 | 53 | } 54 | 55 | this.value = function (){ 56 | if( arguments.length ){ 57 | opts.value = arguments[0]; 58 | this.render(); 59 | } 60 | return opts.value; 61 | } 62 | 63 | this.max = function (){ 64 | if( arguments.length ){ 65 | opts.max = arguments[0]; 66 | opts.range = opts.max - opts.min; 67 | this.render(); 68 | } 69 | return opts.max; 70 | } 71 | 72 | this.min = function (){ 73 | if( arguments.length ){ 74 | opts.min = arguments[0]; 75 | opts.range = opts.max - opts.min; 76 | this.render(); 77 | } 78 | return opts.min; 79 | } 80 | 81 | this.label = function(){ 82 | if( arguments.length ){ 83 | opts.label = arguments[0]; 84 | this.render(); 85 | } 86 | return opts.label; 87 | } 88 | 89 | this.node = document.createElement( "div" ); 90 | this.node.className = "progress-bar"; 91 | 92 | var progress = document.createElement( "div" ); 93 | progress.className = "progress-bar-progress"; 94 | this.node.appendChild( progress ); 95 | 96 | var label = document.createElement( "div" ); 97 | label.className = "progress-bar-label"; 98 | this.node.appendChild( label ); 99 | 100 | if( opts.style ) this.node.setAttribute( 'style', data.$data.style ); 101 | else if( opts.width ){ 102 | var width = opts.width; 103 | if( typeof opts.width === "number" ) width = opts.width + "em"; 104 | this.node.setAttribute( 'style', `width: ${width};` ); 105 | } 106 | 107 | this.render(); 108 | 109 | }; 110 | 111 | var progress_bars = {}; 112 | 113 | var handler = function(func, data){ 114 | 115 | console.info( data ); 116 | if( typeof data.$data.key === "undefined" ) return; // should be an error? 117 | 118 | var key = data.$data.key.toString(); 119 | var ref = progress_bars[key]; 120 | 121 | if( !ref && !data.$data.closed ){ 122 | progress_bars[key] = new ProgressBar( data.$data ); 123 | ref = progress_bars[key]; 124 | if( func ) func.call( this, ref.node ); 125 | } 126 | else if( ref ){ 127 | ref.value(data.$data.value); 128 | if( data.$data.closed ){ 129 | delete(progress_bars[key]); 130 | } 131 | } 132 | 133 | } 134 | 135 | module.exports = { 136 | 137 | init: function(core){ 138 | core.R.on( 'progress.bar', handler.bind( this, function(node){ 139 | //core.shell.insert_node( node, true ); 140 | PubSub.publish( core.Constants.SHELL_INSERT_NODE, [node, true]); 141 | })); 142 | return Promise.resolve() 143 | }, 144 | 145 | createProgressBar: function(opts){ 146 | return new ProgressBar( opts ); 147 | } 148 | 149 | }; -------------------------------------------------------------------------------- /packages/side-panel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "side-panel", 3 | "version": "0.1.0", 4 | "main": "side-panel.js" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/table/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "table", 3 | "version": "0.4.4", 4 | "main": "table.js", 5 | "packageDependencies": [ 6 | "side-panel" 7 | ] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/theme/help/bluesans.css: -------------------------------------------------------------------------------- 1 | 2 | /** this is a stylesheet injected into R help pages */ 3 | 4 | html, body { 5 | background: #002040 !important; 6 | color: white !important; 7 | font-family: segoe ui; 8 | font-size: 15px !important; 9 | } 10 | 11 | body { margin: 1em; } 12 | 13 | 14 | table { 15 | background: inherit; 16 | } 17 | 18 | pre, code { 19 | background: #002280; 20 | font-family: courier new !important; 21 | font-size: 15px; 22 | padding-left: .25em; 23 | padding-right: .25em; 24 | } 25 | 26 | a { 27 | background: inherit !important; 28 | color: white !important; 29 | } 30 | 31 | h1, h2, h3, h4 { 32 | background: #002040 !important; 33 | font-family: inherit !important; 34 | color: white !important; 35 | font-weight: 600 !important; 36 | } 37 | -------------------------------------------------------------------------------- /packages/theme/help/sans.css: -------------------------------------------------------------------------------- 1 | 2 | /** this is a stylesheet injected into R help pages */ 3 | 4 | html, body { 5 | font-family: segoe ui; 6 | font-size: 15px !important; 7 | } 8 | 9 | body { 10 | margin: 2em; 11 | } 12 | 13 | h1, h2, h3, h4 { 14 | font-family: inherit !important; 15 | font-weight: 400 !important; 16 | } 17 | -------------------------------------------------------------------------------- /packages/theme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "theme", 3 | "version": "0.1.0", 4 | "main": "theme.js", 5 | "preferenceGroups": { 6 | "Shell Theme": { 7 | "theme": { 8 | "type": "choice", 9 | "default": "default", 10 | "options": "get_shell_themes" 11 | } 12 | }, 13 | "UI Theme": { 14 | "uitheme": { 15 | "type": "choice", 16 | "default": "default", 17 | "options": "get_ui_themes" 18 | } 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /packages/theme/shell/dark.css: -------------------------------------------------------------------------------- 1 | /****************************************************************/ 2 | /* */ 3 | /* Based on CodeMirror's mbo theme. From mbo.css: */ 4 | /* */ 5 | /* Based on mbonaci's Brackets mbo theme */ 6 | /* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ 7 | /* Create your own: http://tmtheme-editor.herokuapp.com */ 8 | /****************************************************************/ 9 | 10 | .cm-s-dark.CodeMirror { background: #2c2c2c; color: #ffffec; } 11 | .cm-s-dark div.CodeMirror-selected { background: #716C62; } 12 | .cm-s-dark .CodeMirror-line::selection, .cm-s-dark .CodeMirror-line > span::selection, .cm-s-dark .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); } 13 | .cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); } 14 | .cm-s-dark .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; } 15 | .cm-s-dark .CodeMirror-guttermarker { color: white; } 16 | .cm-s-dark .CodeMirror-guttermarker-subtle { color: grey; } 17 | .cm-s-dark .CodeMirror-linenumber { color: #dadada; } 18 | .cm-s-dark .CodeMirror-cursor { border-left: 1px solid #ffffec; } 19 | 20 | .cm-s-dark span.cm-comment { color: #95958a; } 21 | .cm-s-dark span.cm-atom { color: #00a8c6; } 22 | .cm-s-dark span.cm-number { color: #00a8c6; } 23 | 24 | .cm-s-dark span.cm-property, .cm-s-dark span.cm-attribute { color: #9ddfe9; } 25 | .cm-s-dark span.cm-keyword { color: #ffb928; } 26 | .cm-s-dark span.cm-string { color: #ffcf6c; } 27 | .cm-s-dark span.cm-string.cm-property { color: #ffffec; } 28 | 29 | .cm-s-dark span.cm-variable { color: #ffffec; } 30 | .cm-s-dark span.cm-variable-2 { color: #00a8c6; } 31 | .cm-s-dark span.cm-def { color: #ffffec; } 32 | .cm-s-dark span.cm-bracket { color: #fffffc; font-weight: bold; } 33 | .cm-s-dark span.cm-tag { color: #9ddfe9; } 34 | .cm-s-dark span.cm-link { color: #f54b07; } 35 | .cm-s-dark span.cm-error { border-bottom: #636363; color: #ffffec; } 36 | .cm-s-dark span.cm-qualifier { color: #ffffec; } 37 | 38 | .cm-s-dark .CodeMirror-activeline-background { background: #494b41; } 39 | .cm-s-dark .CodeMirror-matchingbracket { color: #ffb928 !important; } 40 | .cm-s-dark .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); } 41 | 42 | /* light spinner */ 43 | 44 | .status-overlay { color: #fff; } 45 | 46 | /* error has a red background */ 47 | 48 | .cm-s-dark .shell-error { 49 | background: rgba(192, 20, 0, .5 ); 50 | color: white; 51 | } 52 | 53 | /* light scrollbars for dark content */ 54 | 55 | #shell-container ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.125); } 56 | #shell-container ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.5); } 57 | .CodeMirror-scrollbar-filler { background: rgba(255, 255, 255, 0.125); } 58 | 59 | /* function tip styling */ 60 | 61 | .cmjs-shell-function-tip { 62 | border: 1px solid #ccc; 63 | background: rgba( 255, 255, 240, 1 ); 64 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 65 | color: #000; 66 | } 67 | 68 | /* progress bars */ 69 | 70 | .progress-bar { 71 | background: #2c2c2c; 72 | border: 1px solid #636363; 73 | } 74 | 75 | .progress-bar-progress { 76 | background: #4e4e4e; 77 | } 78 | 79 | .progress-bar-label { 80 | color: #ffffec; 81 | } 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /packages/theme/shell/elegant.css: -------------------------------------------------------------------------------- 1 | .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom { color: #762; } 2 | .cm-s-elegant span.cm-comment { color: #262; font-style: italic; line-height: 1em; } 3 | .cm-s-elegant span.cm-meta { color: #555; font-style: italic; line-height: 1em; } 4 | .cm-s-elegant span.cm-variable { color: black; } 5 | .cm-s-elegant span.cm-variable-2 { color: #b11; } 6 | .cm-s-elegant span.cm-qualifier { color: #555; } 7 | .cm-s-elegant span.cm-keyword { color: #730; } 8 | .cm-s-elegant span.cm-builtin { color: #30a; } 9 | .cm-s-elegant span.cm-link { color: #762; } 10 | .cm-s-elegant span.cm-error { background-color: #fdd; } 11 | 12 | .cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff; } 13 | .cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid #ccc; background: #eee; color:black !important; } 14 | -------------------------------------------------------------------------------- /packages/theme/shell/night.css: -------------------------------------------------------------------------------- 1 | /* Loosely based on the Midnight Textmate theme */ 2 | 3 | .cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } 4 | .cm-s-night div.CodeMirror-selected { background: #447; } 5 | .cm-s-night .CodeMirror-line::selection, .cm-s-night .CodeMirror-line > span::selection, .cm-s-night .CodeMirror-line > span > span::selection { background: rgba(68, 68, 119, .99); } 6 | .cm-s-night .CodeMirror-line::-moz-selection, .cm-s-night .CodeMirror-line > span::-moz-selection, .cm-s-night .CodeMirror-line > span > span::-moz-selection { background: rgba(68, 68, 119, .99); } 7 | .cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } 8 | .cm-s-night .CodeMirror-guttermarker { color: white; } 9 | .cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; } 10 | .cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } 11 | .cm-s-night .CodeMirror-cursor { border-left: 1px solid white; } 12 | 13 | .cm-s-night span.cm-comment { color: #6900a1; } 14 | .cm-s-night span.cm-atom { color: #845dc4; } 15 | .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } 16 | .cm-s-night span.cm-keyword { color: #599eff; } 17 | .cm-s-night span.cm-string { color: #37f14a; } 18 | .cm-s-night span.cm-meta { color: #7678e2; } 19 | .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } 20 | .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; } 21 | .cm-s-night span.cm-bracket { color: #8da6ce; } 22 | .cm-s-night span.cm-comment { color: #6900a1; } 23 | .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } 24 | .cm-s-night span.cm-link { color: #845dc4; } 25 | .cm-s-night span.cm-error { color: #9d1e15; } 26 | 27 | .cm-s-night .CodeMirror-activeline-background { background: #1C005A; } 28 | .cm-s-night .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } 29 | 30 | #shell-container ::-webkit-scrollbar-track 31 | { 32 | background: rgba(255, 255, 255, 0.125); 33 | } 34 | 35 | #shell-container ::-webkit-scrollbar-thumb 36 | { 37 | background: rgba(255, 255, 255, 0.5); 38 | } 39 | 40 | .CodeMirror-scrollbar-filler { 41 | background: rgba(255, 255, 255, 0.125); 42 | } 43 | -------------------------------------------------------------------------------- /packages/theme/shell/old-school.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * no highlighting, just one color for user and one color for R. 5 | * 6 | * NOTE: using a google web font, so this requires network. 7 | * Cousine, by the great Steve Matteson, is metrically compatible 8 | * with Courier New. If that's useful to you. 9 | */ 10 | 11 | @import "https://fonts.googleapis.com/css?family=Cousine:400"; 12 | 13 | .cm-s-old-school.CodeMirror { 14 | font-family: "Cousine"; 15 | font-weight: 400; 16 | font-size: 10.5pt; 17 | background: white; 18 | color:red; 19 | } 20 | 21 | .cm-s-old-school .shell-text, 22 | .cm-s-old-school .shell-system-information, 23 | .cm-s-old-school .shell-error { 24 | color: darkblue; 25 | } 26 | 27 | .cm-s-old-school .pager { 28 | color: black; 29 | } 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/theme/shell/zenburn.css: -------------------------------------------------------------------------------- 1 | /** 2 | * " 3 | * Using Zenburn color palette from the Emacs Zenburn Theme 4 | * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el 5 | * 6 | * Also using parts of https://github.com/xavi/coderay-lighttable-theme 7 | * " 8 | * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css 9 | */ 10 | 11 | .cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } 12 | .cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; } 13 | .cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white; } 14 | .cm-s-zenburn { background-color: #3f3f3f; color: #dcdccc; } 15 | .cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; } 16 | .cm-s-zenburn span.cm-comment { color: #7f9f7f; } 17 | .cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; } 18 | .cm-s-zenburn span.cm-atom { color: #bfebbf; } 19 | .cm-s-zenburn span.cm-def { color: #dcdccc; } 20 | .cm-s-zenburn span.cm-variable { color: #dfaf8f; } 21 | .cm-s-zenburn span.cm-variable-2 { color: #dcdccc; } 22 | .cm-s-zenburn span.cm-string { color: #cc9393; } 23 | .cm-s-zenburn span.cm-string-2 { color: #cc9393; } 24 | .cm-s-zenburn span.cm-number { color: #dcdccc; } 25 | .cm-s-zenburn span.cm-tag { color: #93e0e3; } 26 | .cm-s-zenburn span.cm-property { color: #dfaf8f; } 27 | .cm-s-zenburn span.cm-attribute { color: #dfaf8f; } 28 | .cm-s-zenburn span.cm-qualifier { color: #7cb8bb; } 29 | .cm-s-zenburn span.cm-meta { color: #f0dfaf; } 30 | .cm-s-zenburn span.cm-header { color: #f0efd0; } 31 | .cm-s-zenburn span.cm-operator { color: #f0efd0; } 32 | .cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; } 33 | .cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; } 34 | .cm-s-zenburn .CodeMirror-activeline { background: #000000; } 35 | .cm-s-zenburn .CodeMirror-activeline-background { background: #000000; } 36 | .cm-s-zenburn div.CodeMirror-selected { background: #545454; } 37 | .cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected { background: #4f4f4f; } 38 | 39 | /** r shell overloads */ 40 | 41 | .cm-s-zenburn span.shell-parse-error { color: #ff4f3f !important; } 42 | .cm-s-zenburn span.pager { 43 | color: white !important; 44 | font-weight: 400 ; 45 | } 46 | 47 | .cm-s-zenburn span.shell-piped-stream { 48 | color: white; 49 | font-weight: 400 ; 50 | } 51 | 52 | .cm-s-zenburn span.shell-prompt { color: #dcdccc; } 53 | .cm-s-zenburn span.shell-prompt-debug { color: #cc9393; } 54 | 55 | 56 | /** UI (FIXME: separate UI themes) */ 57 | 58 | .progress-bar { 59 | background: #f0dfaf; 60 | } 61 | 62 | .progress-bar-progress { 63 | background: #dfaf8f; 64 | } 65 | 66 | .progress-bar-label { 67 | color: #3f3f3f; 68 | } 69 | 70 | .status-overlay { color: #fff; } 71 | 72 | #shell-container ::-webkit-scrollbar-track 73 | { 74 | background: rgba(255, 255, 255, 0.125); 75 | } 76 | 77 | #shell-container ::-webkit-scrollbar-thumb 78 | { 79 | background: rgba(255, 255, 255, 0.5); 80 | } 81 | 82 | .CodeMirror-scrollbar-filler { 83 | background: rgba(255, 255, 255, 0.125); 84 | } 85 | 86 | -------------------------------------------------------------------------------- /packages/theme/theme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | "use strict"; 24 | 25 | // FIXME: we shouldn't be using filenames as identifiers. use 26 | // some sort of parameter in the file (minimally, make it optional) 27 | // OTOH, there's no real CSS support for anything like that... 28 | 29 | const fs = require( "fs" ); 30 | const path = require( "path" ); 31 | const PubSub = require( "pubsub-js" ); 32 | 33 | var Theme = function(opts, core){ 34 | 35 | opts = opts || {}; 36 | 37 | this.dir = opts.dir || "./themes"; 38 | this.default = opts.default || "default"; 39 | this.themes = [ this.default ]; 40 | this.key = opts.key || "theme"; 41 | this.active = this.default; 42 | 43 | this.list_themes = function(){ 44 | var instance = this; 45 | fs.readdir( this.dir, function(err, files){ 46 | if( err ) console.info( "E", err ); 47 | files.map( function( f ){ 48 | instance.themes.push( f.replace( /\.css$/, "" )); 49 | }); 50 | }) 51 | }; 52 | 53 | /** load from settings */ 54 | this.load = function(){ 55 | 56 | var theme = core.Settings[ this.key ] || this.default; 57 | // console.info( this.key, theme ); 58 | this.load_theme( theme, false ); 59 | 60 | }; 61 | 62 | /** load explicitly (by name) */ 63 | this.load_theme = function( theme, save ){ 64 | 65 | // console.info( "load theme", theme ); 66 | if( !theme ) theme = this.default; 67 | 68 | // save to settings 69 | if( save ) core.Settings[ this.key ] = theme ; 70 | 71 | // set field for lookups 72 | this.active = theme; 73 | 74 | // remove existing theme, if any 75 | var elts = document.querySelectorAll( `link[data-target=${this.key}]` ); 76 | for( var i = 0; i< elts.length; i++ ){ 77 | elts[i].parentElement.removeChild( elts[i] ); 78 | } 79 | 80 | // default? don't add 81 | if( theme && theme === this.default ){ 82 | // special 83 | if( this.key === "theme" ){ 84 | //opts.shell.setOption( "theme", this.default ); 85 | //opts.shell.refresh(); 86 | PubSub.publish( core.Constants.SHELL_UPDATE_THEME, this.default); 87 | } 88 | this.active = "default"; 89 | return; 90 | } 91 | 92 | if( !theme.match( /\.css$/ )) theme = theme + ".css"; 93 | 94 | var elt = document.querySelector( "link[data-position=last]" ); 95 | var n = document.createElement( "link" ); 96 | n.setAttribute( "rel", "stylesheet" ); 97 | n.setAttribute( "data-target", this.key ); 98 | n.setAttribute( "href", path.join( this.dir, theme ) ); 99 | 100 | if( elt ) elt.parentElement.insertBefore( n, elt ); 101 | else document.head.appendChild( n ); 102 | 103 | // special 104 | if( this.key === "theme" ){ 105 | //opts.shell.setOption( "theme", theme.replace( /\.css$/, "" )); 106 | //opts.shell.refresh(); 107 | PubSub.publish( core.Constants.SHELL_UPDATE_THEME, theme.replace( /\.css$/, "" )); 108 | } 109 | 110 | }; 111 | 112 | }; 113 | 114 | var shell_theme, ui_theme; 115 | 116 | module.exports = { 117 | 118 | init: function( core ){ 119 | 120 | shell_theme = new Theme({ 121 | default: "default", 122 | dir: path.join( __dirname, "shell" ) 123 | }, core); 124 | 125 | ui_theme = new Theme({ 126 | default: "default", 127 | key: "uitheme", 128 | dir: path.join( __dirname, "ui" ), 129 | }, core); 130 | 131 | PubSub.subscribe( core.Constants.SETTINGS_CHANGE, function(channel, obj){ 132 | switch( obj.key ){ 133 | case "theme": 134 | shell_theme.load_theme( obj.val, false ); 135 | break; 136 | case "uitheme": 137 | ui_theme.load_theme( obj.val, false ); 138 | break; 139 | }; 140 | }); 141 | 142 | shell_theme.list_themes(); 143 | shell_theme.load(); 144 | 145 | ui_theme.list_themes(); 146 | ui_theme.load(); 147 | 148 | return Promise.resolve(); 149 | }, 150 | 151 | get_shell_themes: function(){ 152 | return shell_theme.themes; 153 | }, 154 | 155 | get_ui_themes: function(){ 156 | return ui_theme.themes; 157 | }, 158 | 159 | }; -------------------------------------------------------------------------------- /packages/theme/ui/dark.css: -------------------------------------------------------------------------------- 1 | 2 | /* --- panel and lists ------------------------- */ 3 | 4 | .panel { 5 | background: #222; 6 | color: #fff; 7 | } 8 | 9 | .panel input[type=text] { 10 | background: #eee; 11 | } 12 | 13 | .panel .list-entry { 14 | background: #444; 15 | } 16 | 17 | .panel .list-entry:nth-child(even) { 18 | background: #333; 19 | } 20 | 21 | .panel .list-header, panel-header a { 22 | color: rgb(255, 207, 108); 23 | } 24 | 25 | .panel .sub-header { 26 | color: #bbb; 27 | } 28 | 29 | .watch-content { 30 | background: #333; 31 | } 32 | 33 | .panel-content-header-temp { 34 | color: #fff; 35 | } 36 | 37 | /* --- scrollbars ------------------------------ */ 38 | 39 | .panel ::-webkit-scrollbar-track 40 | { 41 | background: rgba(255, 255, 255, 0.125); 42 | } 43 | 44 | .panel ::-webkit-scrollbar-thumb 45 | { 46 | background: rgba(255, 255, 255, 0.5); 47 | } 48 | 49 | .panel ::-webkit-scrollbar-corner, { 50 | background: rgba(255, 255, 255, 0.125); 51 | } 52 | 53 | 54 | /* --- buttons --------------------------------- */ 55 | 56 | panel-header button { 57 | border: 1px solid #222; 58 | color: #888; 59 | } 60 | 61 | panel-header button:hover { 62 | color: #aaa; 63 | background: #444; 64 | } 65 | 66 | 67 | /* 68 | button.close-panel.panel-close-box { 69 | border: 1px solid #222; 70 | color: #888; 71 | } 72 | 73 | button.close-panel.panel-close-box:hover { 74 | color: #aaa; 75 | background: #444; 76 | } 77 | button.close-panel.panel-close-box:active { 78 | } 79 | */ 80 | 81 | .panel-footer { 82 | border-top: 1px solid #666; 83 | } 84 | 85 | .panel-footer button { 86 | background: #333; 87 | color: #fff; 88 | border-color: #555; 89 | } 90 | 91 | .panel-footer button:hover { 92 | background: #222; 93 | } 94 | 95 | .panel-footer button:active { 96 | background: #444; 97 | } 98 | 99 | /* --- checkbox and radio ---------------------- */ 100 | 101 | input[type=checkbox], input[type=radio] { 102 | color: rgb(0, 168, 198); 103 | } 104 | 105 | input[type=checkbox]:disabled, input[type=radio]:disabled { 106 | color: rgb(0, 168, 198); 107 | } 108 | 109 | /* --- inputs ---------------------------------- */ 110 | 111 | input[type=text]{ 112 | background: rgba( 255,255,255,.125 ) !important; 113 | border-color: #333; 114 | color: #fff; 115 | } 116 | 117 | /* --- dialog ---------------------------------- */ 118 | 119 | html-dialog .dialog-chrome { 120 | background: #333; 121 | border: 1px solid #666; 122 | color: #fff; 123 | } 124 | 125 | html-dialog input, html-dialog select { 126 | background: #eee; 127 | } 128 | 129 | html-dialog .dialog-header { 130 | color: rgb(255, 207, 108); 131 | border-bottom: 1px solid #666; 132 | } 133 | 134 | html-dialog .dialog-header button { 135 | border: 1px solid #222; 136 | color: #888; 137 | } 138 | 139 | html-dialog .dialog-header button:hover { 140 | color: #aaa; 141 | background: #444; 142 | } 143 | 144 | html-dialog .dialog-footer { 145 | border-top: 1px solid #666; 146 | } 147 | 148 | html-dialog .dialog-footer button { 149 | background: #333; 150 | color: #fff; 151 | border-color: #555; 152 | } 153 | 154 | html-dialog .dialog-footer button:hover { 155 | background: #222; 156 | } 157 | 158 | html-dialog .dialog-footer button:active { 159 | background: #444; 160 | } 161 | 162 | /* --- grid (needs work) ---*/ 163 | 164 | display-grid { 165 | background: #222; 166 | } 167 | 168 | display-grid grid-row { 169 | background: #666; 170 | } 171 | 172 | display-grid .grid-cell { 173 | background: #444; 174 | } 175 | 176 | display-grid .grid-header { 177 | background: #333; 178 | } 179 | 180 | display-grid .grid-cell.selected { 181 | border-color: rgba(0,0,0,0); 182 | background: #888; 183 | } 184 | 185 | display-grid .grid-header.selected { 186 | border-color: rgba( 0,0,0,0 ); 187 | background: #eee; 188 | color: #000; 189 | } 190 | 191 | /* alternate rows, except for selection */ 192 | display-grid grid-row:nth-child(odd) .table-cell:not(.selected) { 193 | background: #333; 194 | } 195 | -------------------------------------------------------------------------------- /postcss/button.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | button { 24 | 25 | font-family: inherit; 26 | font-size: inherit; 27 | padding: 3px; 28 | 29 | display: inline-block; 30 | padding: 6px 12px; 31 | margin-bottom: 0; 32 | 33 | font-weight: 400; 34 | line-height: 1.42857143; /* FIXME: this is calculated, but from what? check bs */ 35 | text-align: center; 36 | white-space: nowrap; 37 | vertical-align: middle; 38 | 39 | cursor: pointer; 40 | -webkit-user-select: none; 41 | user-select: none; 42 | background: none; 43 | border-radius: 3px; 44 | background: #fff; 45 | 46 | } 47 | 48 | /** these are perhaps theme-specific? */ 49 | 50 | button { 51 | border: 1px solid #ccc; 52 | } 53 | 54 | button:hover { 55 | background: #eee; 56 | } 57 | 58 | button:active { 59 | border: 1px solid #999; 60 | background: #ccc; 61 | } 62 | -------------------------------------------------------------------------------- /postcss/checkbox2.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | input[type=radio], input[type=checkbox] { 24 | 25 | color: #444; 26 | font-family: 'Material Icons'; 27 | font-weight: normal; 28 | font-style: normal; 29 | text-transform: none; 30 | letter-spacing: normal; 31 | font-size: 22px; 32 | word-wrap: normal; 33 | white-space: nowrap; 34 | direction: ltr; 35 | text-rendering: optimizeLegibility; /* ligatures */ 36 | -webkit-font-smoothing: antialiased; 37 | -webkit-appearance: none; 38 | 39 | /* put in container or style */ 40 | padding: 0; 41 | margin: 0; 42 | 43 | } 44 | 45 | input[type=radio]:disabled, input[type=checkbox]:disabled { 46 | color: #888; 47 | } 48 | 49 | input[type=radio]:disabled::before, input[type=checkbox]:disabled::before { 50 | opacity: .5; 51 | } 52 | 53 | input[type=checkbox]::before { 54 | content: 'check_box_outline_blank'; 55 | } 56 | 57 | input[type=checkbox]:checked::before { 58 | content: 'check_box'; 59 | } 60 | 61 | input[type=radio] { 62 | font-size: 18px; 63 | } 64 | 65 | input[type=radio]::before { 66 | content: 'radio_button_unchecked'; 67 | } 68 | 69 | input[type=radio]:checked::before { 70 | content: 'radio_button_checked'; 71 | } 72 | 73 | input[type=checkbox]:focus::before, input[type=radio]:focus::before, 74 | input[type=checkbox]:focus, input[type=radio]:focus { 75 | outline: none; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /postcss/chooser.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | .chooser-list-footer { 24 | padding: 1em; 25 | flex-grow: 0; 26 | position: relative; 27 | } 28 | 29 | .chooser-list-footer { 30 | border-top: 1px solid #666; 31 | } 32 | 33 | 34 | /* ------------------------- */ 35 | 36 | .chooser-list2 { 37 | height: 100%; 38 | width: 100%; 39 | overflow: auto; 40 | } 41 | 42 | .watch-text { 43 | height: 100%; 44 | position: absolute; 45 | padding: 0 1em 0 1em; 46 | cursor: default; 47 | } 48 | 49 | .watch-content { 50 | background: #fff; 51 | padding: 1em; 52 | line-height: 1.4; 53 | margin: 0; 54 | overflow-y: auto; 55 | overflow-x: hidden; 56 | text-overflow: ellipsis; 57 | } 58 | 59 | /* --- layout specific to mirror chooser ----------------- */ 60 | 61 | mirror-list-entry > div { 62 | padding: 10px 6px 10px 12px; 63 | } 64 | 65 | mirror-list-entry > div:nth-child(2) { 66 | padding-left: 0; 67 | } 68 | 69 | .mirror-list-host { 70 | font-size: .9em; 71 | text-overflow: ellipsis; 72 | } 73 | 74 | /* --- layout specific to package chooser ----------------- */ 75 | 76 | package-list-entry { 77 | padding: 0; 78 | white-space: nowrap; 79 | padding-left: .5em; 80 | position: relative; 81 | display: flex; 82 | align-items: center; 83 | } 84 | 85 | package-list-entry[installed] div { 86 | opacity: .5; 87 | } 88 | 89 | package-list-entry > div { 90 | position: relative; 91 | cursor: default; 92 | line-height: 1; 93 | padding: 12px 4px 12px 8px; 94 | } 95 | 96 | .cran_link, .cran_link:visited, .cran_link:active { 97 | text-decoration: none; 98 | color: inherit; 99 | } 100 | 101 | /* --- layout specific to preferences ----------------- */ 102 | 103 | preferences-option, preferences-size, preferences-input, preferences-select { 104 | padding: 0; 105 | white-space: nowrap; 106 | padding-left: .5em; 107 | position: relative; 108 | display: flex; 109 | align-items: center; 110 | } 111 | 112 | preferences-option > div, preferences-size > div, preferences-input > div, preferences-select > div { 113 | position: relative; 114 | cursor: default; 115 | line-height: 1; 116 | padding: 12px 4px 12px 8px; 117 | } 118 | 119 | a.browse { 120 | cursor: pointer; 121 | line-height: 1; 122 | padding: 12px 4px 12px 8px; 123 | } 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /postcss/dialog.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | html-dialog .dialog-chrome { 24 | background: #fff; 25 | border: 1px solid #666; 26 | } 27 | 28 | html-dialog .dialog-header { 29 | border-bottom: 1px solid #ddd; 30 | } 31 | 32 | html-dialog .dialog-header button { 33 | border: 1px solid rgb(248,248,248); 34 | color: #aaa; 35 | } 36 | 37 | html-dialog .dialog-header button:hover { 38 | color: #fff; 39 | background: #ddd ; 40 | } 41 | 42 | html-dialog .dialog-header button:active { 43 | border: 1px solid #666; 44 | } 45 | 46 | html-dialog .dialog-footer { 47 | text-align: center; 48 | border-top: 1px solid #ddd; 49 | } 50 | -------------------------------------------------------------------------------- /postcss/grid.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | display-grid { 24 | background: #ccc; 25 | } 26 | 27 | #grid-column-header { 28 | background: #ccc; 29 | } 30 | 31 | #grid-row-header-background { 32 | background: #ccc; 33 | } 34 | 35 | .grid-header { 36 | background: #eee; 37 | text-align: center; 38 | cursor: text; 39 | } 40 | 41 | .table-cell { 42 | background: #fff; 43 | cursor: cell; 44 | text-align: right; 45 | } 46 | 47 | .table-cell-date, .table-cell-factor, .table-cell-string { 48 | text-align: center; 49 | } 50 | 51 | .table-cell-left { 52 | text-align: left; 53 | } 54 | 55 | .table-cell.fixed-width { 56 | font-size: 1em; 57 | } 58 | 59 | /* alternate rows, except for selection */ 60 | grid-row:nth-child(odd) .table-cell:not(.selected) { 61 | /* background: #ecf9fe; */ 62 | background: rgb(235, 240, 250); 63 | } 64 | 65 | /* === selections =============================== */ 66 | 67 | .table-cell.selected { 68 | background: lightyellow; 69 | } 70 | 71 | .grid-header.selected { 72 | background: yellow; 73 | } 74 | 75 | .selection-border { 76 | background: yellow; 77 | min-width: 3px; 78 | min-height: 3px; 79 | } 80 | 81 | /* === sort arrows ============================== */ 82 | 83 | .header-column-sort-ascending::after { 84 | content: "▲"; 85 | position: absolute; 86 | opacity: .4; 87 | font-size: .6em; 88 | padding-left: .6em; 89 | padding-top: .4em; 90 | } 91 | 92 | .header-column-sort-descending::after { 93 | content: "▼"; 94 | position: absolute; 95 | opacity: .4; 96 | font-size: .6em; 97 | padding-left: .6em; 98 | padding-top: .4em; 99 | } -------------------------------------------------------------------------------- /postcss/history.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | .history-gutter { 24 | width: 1em; 25 | } 26 | 27 | .history-gutter-marker { 28 | 29 | } 30 | 31 | .history-gutter-marker:after { 32 | content: "x"; 33 | margin-left: .5em; 34 | } 35 | 36 | .history-scrollbar-annotation { 37 | background: rgba( 160, 200, 255, .75 ); 38 | } 39 | 40 | .history-marker-background { 41 | background: rgba( 160, 200, 255, .25 ); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /postcss/locals.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #locals-panel .grid-cell, #watches-panel .grid-cell { 24 | padding: .6em; 25 | cursor: default; 26 | } 27 | 28 | #locals-panel .grid-cell.selected, #watches-panel .grid-cell.selected { 29 | background: #3399ff; 30 | color: #fff; 31 | } 32 | 33 | #locals-panel .selection-border, #watches-panel .selection-border { 34 | background-color: rgba( 0, 0, 0, 0 ); 35 | } 36 | 37 | 38 | /* 39 | #locals-panel grid-row:nth-child(odd) .table-cell:not(.selected) { 40 | background: #fff; 41 | } 42 | */ 43 | -------------------------------------------------------------------------------- /postcss/main.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | @import "checkbox2.css"; 24 | @import "chooser.css"; 25 | @import "panel.css"; 26 | 27 | @import "textbox.css"; 28 | @import "progress.css"; 29 | @import "button.css"; 30 | @import "dialog.css"; 31 | 32 | @import "scrollbars.css"; 33 | @import "grid.css"; 34 | @import "locals.css"; 35 | @import "history.css"; 36 | 37 | html, body { 38 | font-family: "Segoe UI", "Open Sans"; 39 | font-size: 10pt; 40 | padding: 0px; 41 | margin: 0px; 42 | overflow: hidden; 43 | height: 100%; 44 | width: 100%; 45 | } 46 | 47 | body { 48 | top: 0px; 49 | bottom: 0px; 50 | position: absolute; 51 | } 52 | 53 | body.osx { 54 | font-family: "Helvetica Neue"; 55 | } 56 | 57 | body.linux { 58 | font-family: "Liberation Sans"; 59 | font-size: 11pt; 60 | } 61 | 62 | body.ubuntu { 63 | font-family: "Ubuntu"; 64 | font-size: 11pt; 65 | } 66 | 67 | #shell-layout { 68 | margin: 0; 69 | top: 0px; 70 | bottom: 0px; 71 | left: 0px; 72 | right: 0px; 73 | position: absolute; 74 | opacity: 0; 75 | } 76 | 77 | .CodeMirror, .fixed-width { 78 | font-family: consolas, monospace; 79 | font-size: 14.25px; 80 | height: 100%; 81 | } 82 | 83 | .linux .CodeMirror, .linux .fixed-width { 84 | font-family: "Liberation Mono"; 85 | } 86 | 87 | .ubuntu .CodeMirror, .ubuntu .fixed-width { 88 | font-family: "Ubuntu Mono"; 89 | font-size: 13pt; 90 | } 91 | 92 | .osx .CodeMirror, .osx .fixed-width { 93 | font-family: "Menlo"; 94 | font-size: 13px; 95 | } 96 | 97 | .cm-s-default span.shell-parse-error { color: #ff4f3f !important; } 98 | .cm-s-default span.pager { color: #000 !important; } 99 | .cm-s-default span.shell-piped-stream { color: #000 !important; } 100 | .cm-s-default span.shell-prompt-debug { color: red; } 101 | 102 | /** I just don't like the default */ 103 | .cm-s-default .CodeMirror-matchingbracket { outline:1px solid #ccc; background: #ddd; color:black !important; } 104 | 105 | #shell-container { 106 | position: absolute; 107 | width: 100%; 108 | height: 100%; 109 | } 110 | 111 | /* 112 | .shell-parse-error { 113 | color: red !important; 114 | } 115 | */ 116 | 117 | .CodeMirror-hints { 118 | 119 | letter-spacing: .25px; 120 | /* font-weight: bold; */ 121 | 122 | font-family: consolas, "helvetica neue", monospace; 123 | font-size: 11px; 124 | 125 | } 126 | 127 | /* --- status icon --------------------------------------------------- */ 128 | 129 | .status-icon { 130 | width: 1em; 131 | height: 1em; 132 | opacity: 0; 133 | transition: opacity .2s ease-in-out; 134 | border: 0; 135 | margin: 0; 136 | padding: 0; 137 | line-height: 0; 138 | font-size: 40px; 139 | } 140 | 141 | .status-overlay { 142 | color: black; 143 | } 144 | 145 | .status-icon.busy { 146 | opacity: .45; 147 | animation: spin 1.8s linear infinite; 148 | } 149 | 150 | @keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } } 151 | 152 | .overlay-bottom-right { 153 | position: absolute; 154 | bottom: 4px; 155 | right: 4px; 156 | z-index: 9999; 157 | } 158 | 159 | .overlay-bottom-right.scrollbar-offset-x { 160 | right: 13px; /* fixme: depends on scrollbar, so use a variable */ 161 | } 162 | 163 | .overlay-bottom-right.scrollbar-offset-y { 164 | bottom: 13px; /* fixme: depends on scrollbar, so use a variable */ 165 | } 166 | 167 | /* --- toolbar ------------------------------------------------------ */ 168 | 169 | .toolbar { 170 | border-bottom: 1px solid #ccc; 171 | overflow-x: hidden; 172 | display: flex; 173 | flex-direction: row; 174 | padding: 2px; 175 | } 176 | 177 | .toolbar-btn:focus, .toolbar-btn:active { 178 | outline: 0; 179 | } 180 | 181 | .toolbar-btn:active { 182 | background-color: #bbb; 183 | outline: 0; 184 | border: 0; 185 | } 186 | 187 | .toolbar-btn:hover { 188 | background-color: #eee; 189 | cursor: pointer; 190 | } 191 | 192 | .toolbar-btn { 193 | background: none; 194 | border: 0; 195 | background-color: #fff; 196 | color: #666; 197 | padding: 0; 198 | margin: 2px; 199 | padding: 0; 200 | outline: 0; 201 | border-radius: 3px; 202 | } 203 | 204 | .toolbar-btn i { 205 | margin: 2px; 206 | padding: 0; 207 | outline: 0; 208 | display: block; 209 | } 210 | 211 | .toolbar-btn.pull-right { 212 | margin-left: auto; /* hooray! */ 213 | } 214 | 215 | .btn:disabled { 216 | color: #bbb; 217 | } 218 | 219 | .btn:disabled:hover { 220 | background:none; 221 | cursor: default; 222 | } 223 | 224 | .toolbar #find-in-browser { 225 | position: relative; 226 | margin: 2px; 227 | margin-top: 4px; 228 | background: inherit; 229 | margin-left: 1em; 230 | } 231 | 232 | /* --- for inline --- */ 233 | 234 | .CodeMirror .graphics-device { 235 | margin: 1em 1em 1em 1em; 236 | border: 1px solid #ccc; 237 | width: 576px; 238 | height: 360px; 239 | } 240 | 241 | 242 | /* --- for panel --- */ 243 | graphics-panel .graphics-device { 244 | padding: 0; 245 | margin: 0; 246 | } 247 | 248 | .panel-graphics-container { 249 | top: 0px; 250 | left: 0px; 251 | right: 0px; 252 | bottom: 0px; 253 | position: absolute; 254 | } 255 | 256 | /* --- orphans is a block for stuff that's laid out but not displayed --- */ 257 | 258 | #orphans { 259 | 260 | display: none; 261 | position: absolute; 262 | top: 0px; 263 | left: 0px; 264 | 265 | } 266 | 267 | /* --- function tips ---------------------------------------------------- */ 268 | 269 | .cmjs-shell-function-tip-container { 270 | position: absolute; 271 | z-index: 15; 272 | overflow: visible; 273 | opacity: 0; 274 | transition: opacity .1s; 275 | } 276 | 277 | .cmjs-shell-function-tip { 278 | position: relative; 279 | transform: translateY(-100%); 280 | bottom: .5em; 281 | left: -1em; 282 | border: 1px solid #ccc; 283 | background: rgba( 255, 255, 200, .9 ); 284 | padding: 2px; 285 | border-radius: 2px; 286 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 287 | } 288 | 289 | .cmjs-shell-function-tip-container.visible { 290 | opacity: 1; 291 | } 292 | -------------------------------------------------------------------------------- /postcss/panel.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | .panel { 24 | 25 | display: flex; 26 | flex-direction: column; 27 | height: 100%; 28 | width: 100%; 29 | position: absolute; 30 | background: rgb(248,248,248); 31 | color: #222; 32 | 33 | } 34 | 35 | .panel-header { 36 | color: rgb(255, 207, 108); 37 | } 38 | 39 | .panel .sub-header { 40 | color: #999; 41 | } 42 | 43 | panel-header { 44 | border-bottom: 1px dotted #ccc; 45 | order: 0; 46 | } 47 | 48 | panel-header button { 49 | height: 2em; 50 | width: 2em; 51 | margin: 10px 10px 10px 0px; 52 | padding: 0 0; 53 | background: inherit; 54 | text-align: right; 55 | overflow: hidden; 56 | border: 1px solid rgb(248,248,248); 57 | color: #aaa; 58 | } 59 | 60 | panel-header a { 61 | color: #aaa; 62 | } 63 | 64 | panel-header button:hover { 65 | color: #fff; 66 | background: #ddd ; 67 | } 68 | panel-header button:active { 69 | border: 1px solid #666; 70 | } 71 | 72 | .panel-body { 73 | flex-grow: 1; 74 | order: 1; 75 | position: relative; 76 | display: flex; 77 | flex-direction: column; 78 | } 79 | 80 | .panel-footer { 81 | border-top: 1px solid #ccc; 82 | order: 2; 83 | } 84 | 85 | .panel-footer button { 86 | margin: .75em .3em .75em .3em; 87 | background: #fff; 88 | color: #333; 89 | border-color: #aaa; 90 | } 91 | 92 | .panel-footer button:hover { 93 | background: #eee; 94 | } 95 | 96 | .panel-footer button:active { 97 | background: #ddd; 98 | } 99 | 100 | .panel-footer.centered { 101 | text-align: center; 102 | } 103 | 104 | /** flex patch */ 105 | .panel-container { 106 | position: absolute; 107 | height: 100%; 108 | width: 100%; 109 | overflow: hidden; 110 | } 111 | 112 | .panel-content-header { 113 | padding: .5em; 114 | } 115 | 116 | .panel-content-header-temp { 117 | padding: .5em; 118 | text-align: center; 119 | color: #444; 120 | } 121 | 122 | .panel-container2 { 123 | flex-grow: 1; 124 | position: relative; 125 | } 126 | 127 | .panel input[type=text] { 128 | background: #fff; 129 | } 130 | 131 | input[type=text].value-invalid { 132 | background: #f58c56; 133 | } 134 | 135 | .panel .list-entry { 136 | background: rgb( 235, 240, 250 ); 137 | } 138 | 139 | .panel .list-entry:nth-child(even) { 140 | background: #fff; 141 | } 142 | 143 | .panel .list-header { 144 | color: #666; 145 | display: flex; 146 | padding: 1em; 147 | flex-grow: 0; 148 | position: relative; 149 | } 150 | -------------------------------------------------------------------------------- /postcss/progress.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | .progress-bar { 24 | position: relative; 25 | box-sizing: border-box; 26 | width: 80%; 27 | height: 1.4em; 28 | background: #fff; 29 | margin: 1em; 30 | border-radius: 2px; 31 | overflow: hidden; 32 | border: 1px solid #ccc; 33 | } 34 | 35 | .progress-bar-progress { 36 | position: absolute; 37 | box-sizing: border-box; 38 | top: 0px; 39 | left: 0px; 40 | height: 100%; 41 | z-index: 1; 42 | background: #eef; 43 | padding: 0; 44 | margin: 0; 45 | } 46 | 47 | .progress-bar-label { 48 | position: absolute; 49 | box-sizing: border-box; 50 | top: 50%; 51 | left: 0px; 52 | width: 100%; 53 | text-align: center; 54 | z-index: 2; 55 | color: #333; 56 | padding: 0; 57 | margin: 0; 58 | -webkit-transform: translateY(-50%); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /postcss/scrollbars.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /* default scrollbars */ 24 | 25 | ::-webkit-scrollbar 26 | { 27 | width: 12px; 28 | height: 12px; 29 | } 30 | 31 | ::-webkit-scrollbar-track 32 | { 33 | background: rgba(0, 0, 0, 0.125); 34 | } 35 | 36 | ::-webkit-scrollbar-thumb 37 | { 38 | background: rgba(0, 0, 0, 0.125); 39 | 40 | /* 41 | * does not work. 42 | * see https://bugs.webkit.org/show_bug.cgi?id=104412 43 | */ 44 | transition: background-color 1s; 45 | } 46 | 47 | ::-webkit-scrollbar-thumb:hover 48 | { 49 | background: rgba(0, 0, 0, 0.25); 50 | } 51 | 52 | ::-webkit-scrollbar-corner, 53 | .CodeMirror-scrollbar-filler { 54 | background: rgba(0, 0, 0, 0.125); 55 | } 56 | -------------------------------------------------------------------------------- /postcss/textbox.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Structured Data, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | input[type=text] { 24 | font-size: inherit; 25 | border-radius: 3px; 26 | border: 1px solid #ccc; 27 | height: 1.5em; 28 | padding-left: .35em; 29 | } 30 | 31 | select { 32 | font-size: inherit; 33 | border-radius: 3px; 34 | border: 1px solid #ccc; 35 | height: 1.8em; 36 | padding-left: .35em; 37 | } 38 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var fs = require( "fs" ); 3 | var ext = fs.readdirSync('node_modules').filter( function( name ){ 4 | return( !name.match( /^\./ )); 5 | }); 6 | 7 | module.exports = { 8 | entry: "./core/renderer.js", 9 | output: { 10 | filename: "./app/core.js" 11 | }, 12 | externals: [ 13 | function( context, request, callback ){ 14 | if(/^[a-z\-0-9]+$/.test(request) || /plugin/.test(request) || /packages/.test(request)){ 15 | return callback( null, "require('" + request + "');" ); 16 | } 17 | callback(); 18 | } 19 | ], 20 | target: 'node', 21 | devtool: 'inline-source-map', 22 | plugins: [ 23 | // new webpack.optimize.UglifyJsPlugin({}) 24 | ], 25 | node: { 26 | __dirname: false 27 | } 28 | } 29 | 30 | --------------------------------------------------------------------------------