├── .nvmrc ├── .env.test ├── tsconfig.prod.json ├── src ├── react-app-env.d.ts ├── Images │ ├── icon.afdesign │ ├── fileNew.afdesign │ ├── fileOpen.afdesign │ ├── fileSave.afdesign │ ├── untitled.afdesign │ ├── addToScript.afdesign │ ├── fileDelete.afdesign │ ├── fileSaveAs.afdesign │ ├── fileSaveAs2.afdesign │ ├── parseJSON.afdesign │ ├── addParameter.afdesign │ ├── fileNewBlack.afdesign │ ├── hamburgerMenu.afdesign │ ├── app-icons │ │ ├── bashwizard.png │ │ ├── bashwizard.afdesign │ │ ├── mac │ │ │ └── bashwizard.icns │ │ ├── win │ │ │ └── bashwizard.ico │ │ ├── png │ │ │ ├── bashwizard_16x16.png │ │ │ ├── bashwizard_32x32.png │ │ │ ├── bashwizard_48x48.png │ │ │ ├── bashwizard_64x64.png │ │ │ ├── bashwizard_128x128.png │ │ │ └── bashwizard_256x256.png │ │ ├── web │ │ │ └── bashwizard_favicon.ico │ │ └── bashwizard.svg │ ├── deleteparameter.afdesign │ ├── vsCodeDebugInfo.afdesign │ ├── fileOpen.svg │ ├── images.tsx │ ├── addParameter.svg │ ├── fileSave.svg │ ├── hamburgerMenu.svg │ ├── deleteParameter.svg │ ├── fileNew.svg │ ├── addToScript.svg │ ├── fileNewBlack.svg │ ├── fileSaveAs2.svg │ ├── fileSaveAs.svg │ ├── parseJSON.svg │ └── vsCodeDebugInfo.svg ├── types │ ├── typings.d.ts │ ├── images.d.ts │ └── old │ │ └── index.d.ts ├── electron │ ├── ipcProxy.ts │ ├── titlebarHelper.ts │ ├── start.js │ ├── ipcMainProxy.ts │ ├── mainServiceProxy.ts │ ├── bwMainService.ts │ └── main.ts ├── index.css ├── index.tsx ├── deferred.ts ├── Components │ ├── bwError.css │ ├── bwError.tsx │ ├── ParameterView.css │ ├── acewrapper.tsx │ ├── askUserYesNoDlg.tsx │ ├── globalScriptState.tsx │ ├── titleBar.scss │ ├── titleBar.tsx │ └── ParameterView.tsx ├── Models │ └── bwCommonModels.ts ├── ipcRendererProxy.ts ├── Themes │ ├── vott-theme.scss │ ├── titleBar-light.scss │ ├── titleBar-dark.scss │ └── dark │ │ └── theme.css ├── Pages │ └── MainPage.css ├── registerServiceWorker.ts └── logo.svg ├── procfile ├── readme ├── CVD.png ├── tabs.PNG ├── tabs.png ├── layout.png ├── logging.PNG ├── toolbar.PNG ├── toolbar.png ├── verbose.PNG ├── verbose.png ├── add param.png ├── input json.png ├── same names.PNG ├── same names.png ├── 300x300icon.png ├── bash wizard.png ├── debug config.png ├── blank parameter.png ├── parameter-type.PNG ├── parameter-type.png ├── input json dialog.png ├── inputFileSupport.PNG ├── inputFileSupport.png ├── optional features.png ├── add-built-in-param.png ├── bash wizard (720x1080).png └── bash wizard (1080x1080).png ├── public ├── bashwizard_favicon.ico ├── manifest.json └── index.html ├── config ├── webpack.dev.js ├── webpack.prod.js └── webpack.common.js ├── .env ├── tsconfig.test.json ├── .editorconfig ├── .vscode └── launch.json ├── electron-builder.yml ├── tslint.json ├── .gitignore ├── LICENSE ├── .devcontainer └── devcontainer.json ├── tsconfig.json ├── ignore └── test.sh ├── test ├── test.sh ├── createKeyVault2.sh └── createKeyVault3.sh └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.15.1 2 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | HOST_TYPE=electron 2 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /procfile: -------------------------------------------------------------------------------- 1 | react: npm run react-start 2 | electron: npm run electron-start -------------------------------------------------------------------------------- /readme/CVD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/CVD.png -------------------------------------------------------------------------------- /readme/tabs.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/tabs.PNG -------------------------------------------------------------------------------- /readme/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/tabs.png -------------------------------------------------------------------------------- /readme/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/layout.png -------------------------------------------------------------------------------- /readme/logging.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/logging.PNG -------------------------------------------------------------------------------- /readme/toolbar.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/toolbar.PNG -------------------------------------------------------------------------------- /readme/toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/toolbar.png -------------------------------------------------------------------------------- /readme/verbose.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/verbose.PNG -------------------------------------------------------------------------------- /readme/verbose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/verbose.png -------------------------------------------------------------------------------- /readme/add param.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/add param.png -------------------------------------------------------------------------------- /readme/input json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/input json.png -------------------------------------------------------------------------------- /readme/same names.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/same names.PNG -------------------------------------------------------------------------------- /readme/same names.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/same names.png -------------------------------------------------------------------------------- /readme/300x300icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/300x300icon.png -------------------------------------------------------------------------------- /readme/bash wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/bash wizard.png -------------------------------------------------------------------------------- /readme/debug config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/debug config.png -------------------------------------------------------------------------------- /readme/blank parameter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/blank parameter.png -------------------------------------------------------------------------------- /readme/parameter-type.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/parameter-type.PNG -------------------------------------------------------------------------------- /readme/parameter-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/parameter-type.png -------------------------------------------------------------------------------- /src/Images/icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/icon.afdesign -------------------------------------------------------------------------------- /readme/input json dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/input json dialog.png -------------------------------------------------------------------------------- /readme/inputFileSupport.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/inputFileSupport.PNG -------------------------------------------------------------------------------- /readme/inputFileSupport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/inputFileSupport.png -------------------------------------------------------------------------------- /readme/optional features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/optional features.png -------------------------------------------------------------------------------- /src/Images/fileNew.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileNew.afdesign -------------------------------------------------------------------------------- /src/Images/fileOpen.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileOpen.afdesign -------------------------------------------------------------------------------- /src/Images/fileSave.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileSave.afdesign -------------------------------------------------------------------------------- /src/Images/untitled.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/untitled.afdesign -------------------------------------------------------------------------------- /public/bashwizard_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/public/bashwizard_favicon.ico -------------------------------------------------------------------------------- /readme/add-built-in-param.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/add-built-in-param.png -------------------------------------------------------------------------------- /src/Images/addToScript.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/addToScript.afdesign -------------------------------------------------------------------------------- /src/Images/fileDelete.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileDelete.afdesign -------------------------------------------------------------------------------- /src/Images/fileSaveAs.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileSaveAs.afdesign -------------------------------------------------------------------------------- /src/Images/fileSaveAs2.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileSaveAs2.afdesign -------------------------------------------------------------------------------- /src/Images/parseJSON.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/parseJSON.afdesign -------------------------------------------------------------------------------- /src/types/typings.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module "*.sh" { 3 | const value: string; 4 | export default value; 5 | } -------------------------------------------------------------------------------- /readme/bash wizard (720x1080).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/bash wizard (720x1080).png -------------------------------------------------------------------------------- /src/Images/addParameter.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/addParameter.afdesign -------------------------------------------------------------------------------- /src/Images/fileNewBlack.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/fileNewBlack.afdesign -------------------------------------------------------------------------------- /src/Images/hamburgerMenu.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/hamburgerMenu.afdesign -------------------------------------------------------------------------------- /readme/bash wizard (1080x1080).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/readme/bash wizard (1080x1080).png -------------------------------------------------------------------------------- /src/Images/app-icons/bashwizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/bashwizard.png -------------------------------------------------------------------------------- /src/Images/deleteparameter.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/deleteparameter.afdesign -------------------------------------------------------------------------------- /src/Images/vsCodeDebugInfo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/vsCodeDebugInfo.afdesign -------------------------------------------------------------------------------- /src/Images/app-icons/bashwizard.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/bashwizard.afdesign -------------------------------------------------------------------------------- /src/Images/app-icons/mac/bashwizard.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/mac/bashwizard.icns -------------------------------------------------------------------------------- /src/Images/app-icons/win/bashwizard.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/win/bashwizard.ico -------------------------------------------------------------------------------- /src/Images/app-icons/png/bashwizard_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/png/bashwizard_16x16.png -------------------------------------------------------------------------------- /src/Images/app-icons/png/bashwizard_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/png/bashwizard_32x32.png -------------------------------------------------------------------------------- /src/Images/app-icons/png/bashwizard_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/png/bashwizard_48x48.png -------------------------------------------------------------------------------- /src/Images/app-icons/png/bashwizard_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/png/bashwizard_64x64.png -------------------------------------------------------------------------------- /src/Images/app-icons/png/bashwizard_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/png/bashwizard_128x128.png -------------------------------------------------------------------------------- /src/Images/app-icons/png/bashwizard_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/png/bashwizard_256x256.png -------------------------------------------------------------------------------- /src/Images/app-icons/web/bashwizard_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelong01/BashWizard/HEAD/src/Images/app-icons/web/bashwizard_favicon.ico -------------------------------------------------------------------------------- /src/types/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | declare module '*.jpeg' 5 | declare module '*.gif' 6 | declare module '*.bmp' 7 | declare module '*.tiff' 8 | -------------------------------------------------------------------------------- /src/electron/ipcProxy.ts: -------------------------------------------------------------------------------- 1 | export interface IpcProxyMessage { 2 | id: string; 3 | type: string; 4 | args?: any; 5 | error?: string; 6 | result?: TResult; 7 | debug?: string; 8 | } 9 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./webpack.common.js') 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: "inline-source-map", 7 | }) -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./webpack.common.js') 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | devtool: 'cheap-module-source-map', 7 | }) -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; 5 | position: relative; 6 | width: 100%; 7 | height: 100%; 8 | overflow: hidden; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | # react-scripts build use this to generate the right path for assets 3 | # relative to index.html 4 | # without it, you'll see error like this 5 | # Failed to load resource: net::ERR_FILE_NOT_FOUND /favicon.ico:1 6 | PUBLIC_URL=. -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "rules": { 7 | "no-unused-variable": false 8 | }, 9 | "defaultSeverity": "warning", 10 | "linterOptions": { 11 | "exclude": [ 12 | "config/**/*.js", 13 | "node_modules/**/*.ts" 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bash Wizard", 3 | "name": "Create Bash Scripts", 4 | "icons": [ 5 | { 6 | "src": "bashwizard_favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # Default: 4 | # https://github.com/editorconfig/editorconfig-defaults/blob/master/editorconfig-defaults.json 5 | 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | # Unix-style newlines with a newline ending every file 10 | [*] 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = space 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /src/Images/fileOpen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Images/images.tsx: -------------------------------------------------------------------------------- 1 | export const svgFiles = 2 | { 3 | FileNew: require("./fileNew.svg"), 4 | FileNewBlack: require("./fileNewBlack.svg"), 5 | DeleteParameter: require("./deleteParameter.svg"), 6 | AddParameter: require("./addParameter.svg"), 7 | SaveToBash: require("./addToScript.svg"), 8 | FileOpen: require("./fileOpen.svg"), 9 | FileSave: require("./fileSave.svg"), 10 | FileSaveAs: require("./fileSaveAs2.svg"), 11 | HamburgerMenu: require("./hamburgerMenu.svg"), 12 | DebugInfo: require("./vsCodeDebugInfo.svg"), 13 | Refresh: require("./parseJSON.svg") 14 | } 15 | 16 | export default svgFiles; 17 | -------------------------------------------------------------------------------- /src/Images/addParameter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | const path = require("path"); 3 | 4 | module.exports = { 5 | target: "electron-main", 6 | entry: "./src/electron/main.ts", 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.ts?$/, 11 | use: [{ 12 | loader: "ts-loader", 13 | options: { 14 | compilerOptions: { 15 | noEmit: false 16 | } 17 | } 18 | }], 19 | exclude: /node_modules/ 20 | } 21 | ] 22 | }, 23 | resolve: { 24 | extensions: [".ts", ".js"] 25 | }, 26 | output: { 27 | filename: "main.js", 28 | path: path.resolve(__dirname, "../build") 29 | } 30 | }; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'primereact/resources/themes/nova-light/theme.css'; 2 | import 'primereact/resources/primereact.min.css'; 3 | import 'primeicons/primeicons.css'; 4 | import 'primeflex/primeflex.css' 5 | import "./index.css" 6 | import "./Components/ParameterView.css" 7 | import "./Components/bwError.css" 8 | import "./Pages/MainPage.css" 9 | import "@fortawesome/fontawesome-free/css/all.css" 10 | import * as React from 'react'; 11 | import * as ReactDOM from 'react-dom'; 12 | import registerServiceWorker from './registerServiceWorker'; 13 | import MainPage from "./Pages/MainPage" 14 | 15 | ReactDOM.render( 16 | , 17 | document.getElementById('root') as HTMLElement 18 | ); 19 | 20 | registerServiceWorker(); 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "attach", 7 | "name": "Attach to Chrome", 8 | "port": 9222, 9 | "webRoot": "${workspaceFolder}" 10 | }, 11 | { 12 | "name": "Launch localhost", 13 | "type": "chrome", 14 | "request": "launch", 15 | "url": "http://localhost:3000", 16 | "webRoot": "${workspaceFolder}/wwwroot" 17 | }, 18 | { 19 | "name": "Launch index.html", 20 | "type": "chrome", 21 | "request": "launch", 22 | "file": "${workspaceFolder}/index.html" 23 | }, 24 | ] 25 | } -------------------------------------------------------------------------------- /electron-builder.yml: -------------------------------------------------------------------------------- 1 | # only use if package.json doesn't contain a "build" 2 | 3 | directories: 4 | output: releases 5 | buildResources: app-icons # this is where app-icons is store 6 | appId: BashWizard 7 | artifactName: '${productName}-${os}.${ext}' 8 | extends: null # need this otherwise it won't use the entry point we set in "main" in package.json 9 | files: 10 | - filter: 11 | - build/ # copy this directory to the asar directory that electron-builder use to look for the main entry file 12 | mac: 13 | icon: src/images/app-icons/mac/bashwizard.icns 14 | target: dmg 15 | identity: null # don't sign the app 16 | darkModeSupport: true 17 | win: 18 | icon: src/images/app-icons/win/bashwizard.ico 19 | linux: 20 | target: 21 | - snap 22 | publish: null 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": [ 5 | "config/**/*.js", 6 | "node_modules/**/*.ts", 7 | "coverage/lcov-report/*.js", 8 | "src/images/*.*" 9 | ] 10 | }, 11 | "jsRules": {}, 12 | "rules": { 13 | "object-literal-sort-keys": false, 14 | "member-ordering": false, 15 | "jsx-no-lambda": false, 16 | "no-console": false, 17 | "ordered-imports": false, 18 | "prefer-const": false, 19 | "variable-name": false, 20 | "no-debugger": false, 21 | "no-shadowed-variable": false, 22 | "no-string-literal": false, 23 | "no-bitwise": false, 24 | "function-constructor": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/deferred.ts: -------------------------------------------------------------------------------- 1 | export interface IDeferred { 2 | resolve(result?: T): void; 3 | reject(err?: any): void; 4 | then(value: T): Promise; 5 | catch(err: any): Promise; 6 | } 7 | 8 | export class Deferred implements IDeferred { 9 | public promise: Promise; 10 | 11 | constructor() { 12 | this.promise = new Promise((resolve, reject) => { 13 | this.resolve = resolve; 14 | this.reject = reject; 15 | }); 16 | 17 | this.then = this.promise.then.bind(this.promise); 18 | this.catch = this.promise.catch.bind(this.promise); 19 | } 20 | // tslint:disable-next-line 21 | public resolve = (result?: T) => { }; 22 | // tslint:disable-next-line 23 | public reject = (err?: any) => { }; 24 | public then = (value: T) => { throw new Error("Not implemented yet"); }; 25 | public catch = (err: any) => { throw new Error("Not implemented yet"); }; 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 24 | 25 | /.vscode 26 | 27 | # dependencies 28 | /node_modules 29 | /.pnp 30 | /test-output 31 | .pnp.js 32 | 33 | # testing 34 | /coverage 35 | 36 | # packaging 37 | /build 38 | 39 | # releases 40 | /releases/ 41 | 42 | # misc 43 | .DS_Store 44 | .env.local 45 | .env.development.local 46 | .env.test.local 47 | .env.production.local 48 | 49 | npm-debug.log* 50 | yarn-debug.log* 51 | yarn-error.log* 52 | 53 | # dev 54 | secrets.sh 55 | 56 | # ide 57 | .idea 58 | 59 | # complexity reports 60 | es6-src/ 61 | report/ 62 | 63 | /dist/* 64 | /ignore/* 65 | -------------------------------------------------------------------------------- /src/Images/fileSave.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/bwError.css: -------------------------------------------------------------------------------- 1 | /* error list support */ 2 | .error-message-tab-panel 3 | { 4 | position: absolute; 5 | width: calc(100% - 2em); 6 | height: calc(100% - 4em); 7 | margin: 0px 1em; 8 | left: 0em; 9 | padding: 0em; 10 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 11 | font-weight: 400; 12 | } 13 | .bw-error-listbox{ 14 | position: relative; 15 | border: none; 16 | overflow: auto; 17 | height: calc(100% - 3em); 18 | width: 100%; 19 | 20 | } 21 | 22 | .bw-error-header { 23 | width: 100%; 24 | } 25 | 26 | .bw-header-col1{ 27 | display: inline-block; 28 | width: 4em; 29 | font-size: 1.5em; 30 | } 31 | .bw-header-col2{ 32 | display: inline-block; 33 | margin-left: 1.5em; 34 | font-size: 1.5em; 35 | } 36 | 37 | .bw-error-item { 38 | height: 2em; 39 | overflow: hidden; 40 | 41 | width: 100%; 42 | } 43 | .bw-error-span { 44 | margin-right: 2em; 45 | margin-top: 0em; 46 | font-size: 1.25em; 47 | } 48 | 49 | .error-col1 { 50 | display: inline-block; 51 | width: 4em; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/electron/titlebarHelper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this class gets information from the main process and passes it back tothe the titlebar 3 | */ 4 | import {BrowserWindow} from "electron"; 5 | 6 | export class TitleBarHelper { 7 | private myBrowserWindow:BrowserWindow | undefined = undefined; 8 | constructor (mainWindow: BrowserWindow) { 9 | this.myBrowserWindow = mainWindow; 10 | mainWindow.on("maximize", () => this.onPositionChanged("maximized")) 11 | mainWindow.on("unmaximize", () => this.onPositionChanged("unmaximized")) 12 | mainWindow.on("minimize", () => this.onPositionChanged("minimize")) 13 | mainWindow.on("restore", () => this.onPositionChanged("restore")) 14 | mainWindow.on("enter-full-screen", () => this.onPositionChanged("enter-full-screen")) 15 | mainWindow.on("leave-full-screen", () => this.onPositionChanged("leave-full-screen")) 16 | } 17 | 18 | private onPositionChanged= (newPosition:string):void => { 19 | this.myBrowserWindow!.webContents.send("window-position-changed", newPosition); 20 | } 21 | } 22 | 23 | export default TitleBarHelper; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Joe Long 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node 3 | { 4 | "name": "BashWizard", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/typescript-node:0-20", 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "ms-vscode-remote.remote-containers", 11 | "ms-edgedevtools.vscode-edge-devtools" 12 | ] 13 | } 14 | } 15 | 16 | // Features to add to the dev container. More info: https://containers.dev/features. 17 | // "features": {}, 18 | 19 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 20 | 21 | 22 | // Use 'postCreateCommand' to run commands after the container is created. 23 | // "postCreateCommand": "yarn install", 24 | 25 | // Configure tool-specific properties. 26 | // "customizations": {}, 27 | 28 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 29 | // "remoteUser": "root" 30 | } 31 | -------------------------------------------------------------------------------- /src/Images/hamburgerMenu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "es6", 6 | "lib": [ 7 | "es2015", 8 | "dom" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": false, 12 | "jsx": "preserve", 13 | "moduleResolution": "node", 14 | "rootDir": "src", 15 | "forceConsistentCasingInFileNames": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noImplicitAny": false, 19 | "importHelpers": true, 20 | "strictNullChecks": true, 21 | "suppressImplicitAnyIndexErrors": true, 22 | "noUnusedLocals": false, 23 | "strictPropertyInitialization": false, 24 | "esModuleInterop": true, 25 | "resolveJsonModule": true, 26 | "noUnusedParameters": false, 27 | "skipLibCheck": false, 28 | "allowSyntheticDefaultImports": true, 29 | "strict": false, 30 | "isolatedModules": true, 31 | "noEmit": true, 32 | "experimentalDecorators": true 33 | }, 34 | "rules": { 35 | "named-imports-order": "any", 36 | "object-literal-sort-keys": false 37 | }, 38 | "include": [ 39 | "src" 40 | ], 41 | "typeRoots": [ 42 | "../node_modules/@types", 43 | "./src/types" 44 | ], 45 | "exclude": [ 46 | "node_modules", 47 | "build", 48 | "scripts", 49 | "acceptance-tests", 50 | "webpack", 51 | "jest", 52 | "src/setupTests.ts" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/Components/bwError.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IErrorMessage } from 'bash-models/commonModel'; 3 | import { ParameterModel } from "bash-models/ParameterModel" 4 | 5 | interface IErrorProps { 6 | ErrorMessage: IErrorMessage; 7 | clicked?(error: BWError): void; 8 | 9 | } 10 | 11 | interface IErrorState { 12 | severity: "warn" | "error" | "info"; 13 | message: string; 14 | Parameter?: ParameterModel 15 | } 16 | 17 | export class BWError extends React.PureComponent { 18 | constructor(props: IErrorProps) { 19 | super(props); 20 | this.state = { 21 | severity: props.ErrorMessage.severity, 22 | message: props.ErrorMessage.message, 23 | Parameter: props.ErrorMessage.Parameter 24 | } 25 | } 26 | private onErrorClicked = (e: React.MouseEvent) => { 27 | e.bubbles = false; 28 | if (this.props.clicked !== undefined) { 29 | this.props.clicked(this); 30 | } 31 | } 32 | 33 | get Parameter(): ParameterModel | undefined { 34 | return this.state.Parameter; 35 | } 36 | 37 | public render() { 38 | return ( 39 |
) => { this.onErrorClicked(e) }}> 40 | {this.state.severity} 41 | {this.state.message} 42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Images/deleteParameter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/ParameterView.css: -------------------------------------------------------------------------------- 1 | .parameter-layout-root { 2 | background-color: transparent; 3 | margin-bottom: 0.25em; 4 | position: relative; 5 | margin: .5em; 6 | } 7 | 8 | .parameter-item-grid { 9 | position: relative; 10 | border-radius: .25em; 11 | margin: 0.5em .5em 0em .5em; 12 | width: calc(100% - 1.0em); 13 | transition: all 10ms linear; 14 | } 15 | 16 | .parameter-item-grid-collapsed { 17 | visibility: collapse; 18 | opacity: 0.0; 19 | } 20 | .TheWholeThing{ 21 | margin: 0em; 22 | } 23 | 24 | .parameter-fieldset { 25 | margin: 0em .5em; 26 | border: 1px solid black; 27 | border-radius: .25em; 28 | transition: all 100ms linear; 29 | transition-delay: 25ms; 30 | } 31 | 32 | .parameter-fieldset-collapsed { 33 | height: 2em !important; 34 | overflow: hidden; 35 | border: 0em; 36 | } 37 | 38 | 39 | .parameter-fieldset-selected { 40 | border: 3px solid blue; 41 | border-radius: .25em; 42 | } 43 | 44 | .param-label { 45 | margin-bottom: .5em !important; 46 | } 47 | 48 | .param-input { 49 | width: 100%; 50 | } 51 | 52 | .param-column { 53 | width: 33%; 54 | } 55 | 56 | .checkbox-grid { 57 | 58 | margin: 0px; 59 | } 60 | 61 | .p-checkbox-label { 62 | margin: 0em .5em; 63 | } 64 | 65 | .collapse-button { 66 | left: 0.125em; 67 | height: 1em; 68 | top: .25em; 69 | width: 1em !important; 70 | z-index: 10; 71 | position: absolute;; 72 | border: 1px solid blue !important; 73 | } 74 | .TheWholeThing { 75 | margin: 0.5em; 76 | } 77 | -------------------------------------------------------------------------------- /src/Images/fileNew.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Images/addToScript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Models/bwCommonModels.ts: -------------------------------------------------------------------------------- 1 | import { GrowlMessage } from 'primereact/growl'; 2 | import { IScriptModelState } from "bash-models/commonModel"; 3 | import { FileFilter } from "electron"; 4 | 5 | 6 | 7 | 8 | export type NotifyScriptModelChanged = (newState: Pick | IScriptModelState) => void; 9 | 10 | export interface IBashWizardMainService { 11 | 12 | readText(filePath: string): Promise; 13 | writeText(filePath: string, contents: string): Promise; 14 | getOpenFile(title: string, extensions: FileFilter[]): Promise; 15 | getSaveFile(title: string, extensions: FileFilter[]): Promise; 16 | getAndApplySettings(): Promise; 17 | saveAndApplySettings(settings: IBashWizardSettings): Promise; 18 | updateSetting(setting: Partial): Promise; 19 | setWindowTitle(title: string): Promise; 20 | } 21 | 22 | export enum BashWizardTheme { 23 | Dark = "dark", 24 | Light = "light" 25 | } 26 | 27 | export interface IBashWizardSettings { 28 | autoSave: boolean; 29 | theme: BashWizardTheme; 30 | autoUpdate: boolean; 31 | showDebugger: boolean; 32 | } 33 | 34 | 35 | export type IGrowlCallback = (message: GrowlMessage | GrowlMessage[]) => void; 36 | 37 | 38 | export interface IAsyncMessage { 39 | fileName: string; 40 | event: "file-changed"; 41 | type: string; 42 | } 43 | 44 | export interface IConvertMessage { 45 | fileName: string; 46 | script: string; 47 | directory: string; 48 | fullFile: string; 49 | } 50 | 51 | export interface IOpenMessage { 52 | fileName: string; 53 | contents: string; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | 23 | 24 | Bash Wizard 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Images/fileNewBlack.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/ipcRendererProxy.ts: -------------------------------------------------------------------------------- 1 | import shortid from "shortid" 2 | import { IpcProxyMessage } from "./electron/ipcProxy"; 3 | import { Deferred } from "./deferred"; 4 | 5 | export class IpcRendererProxy { 6 | 7 | public static pending: { [id: string]: Deferred } = {}; 8 | 9 | public static initialize() { 10 | if (IpcRendererProxy.initialized) { 11 | return; 12 | } 13 | 14 | IpcRendererProxy.ipcRenderer = (window as any).require("electron").ipcRenderer; 15 | IpcRendererProxy.ipcRenderer.on("ipc-renderer-proxy", (sender:any, message: IpcProxyMessage) => { 16 | const deferred = IpcRendererProxy.pending[message.id]; 17 | 18 | if (!deferred) { 19 | throw new Error(`Cannot find deferred with id '${message.id}'`); 20 | } 21 | 22 | if (message.error) { 23 | deferred.reject(message.error); 24 | } else { 25 | deferred.resolve(message.result); 26 | } 27 | 28 | delete IpcRendererProxy.pending[message.id]; 29 | }); 30 | 31 | IpcRendererProxy.initialized = true; 32 | } 33 | 34 | public static send(type: string, args?: TArgs): Promise { 35 | IpcRendererProxy.initialize(); 36 | 37 | const id = shortid.generate(); 38 | const deferred = new Deferred(); 39 | IpcRendererProxy.pending[id] = deferred; 40 | 41 | const outgoingArgs: IpcProxyMessage = { 42 | id, 43 | type, 44 | args, 45 | }; 46 | 47 | IpcRendererProxy.ipcRenderer.send("ipc-main-proxy", outgoingArgs); 48 | 49 | return deferred.promise; 50 | } 51 | private static ipcRenderer:any; 52 | private static initialized: boolean = false; 53 | } 54 | -------------------------------------------------------------------------------- /src/electron/start.js: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | const net = require("net"); 3 | const port = process.env.PORT ? process.env.PORT - 100 : 3000; 4 | 5 | process.env.ELECTRON_START_URL = `http://localhost:${port}`; 6 | 7 | const client = new net.Socket(); 8 | 9 | let startedElectron = false; 10 | const tryConnection = () => 11 | client.connect({ port: port }, () => { 12 | client.end(); 13 | if (!startedElectron) { 14 | console.log("starting electron"); 15 | startedElectron = true; 16 | const exec = require("child_process").exec; 17 | const electron = exec( 18 | "npm run electron:run:dev", 19 | (error, stdout, stderr) => { 20 | console.log("Electron Process Terminated [reason=%s] [error=%o]", error, error); 21 | } 22 | ); 23 | 24 | electron.stdout.on("data", data => { 25 | console.log(data); 26 | }); 27 | 28 | electron.on("message", (message, sendHandle) => { 29 | console.log(message); 30 | }); 31 | 32 | electron.on("error", err => { 33 | console.log(err); 34 | }); 35 | 36 | electron.on("exit", (code, signal) => { 37 | console.log(`Exit-Code: ${code}`); 38 | console.log(`Exit-Signal: ${signal}`); 39 | }); 40 | 41 | electron.on("close", (code, signal) => { 42 | console.log(`Close-Code: ${code}`); 43 | console.log(`Close-Signal: ${signal}`); 44 | }); 45 | 46 | electron.on("disconnect", () => { 47 | console.log("Electron Process Disconnect"); 48 | }); 49 | } 50 | }); 51 | 52 | tryConnection(); 53 | 54 | client.on("error", error => { 55 | setTimeout(tryConnection, 1000); 56 | }); 57 | -------------------------------------------------------------------------------- /src/Images/fileSaveAs2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/acewrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AceEditor from 'react-ace'; 3 | import "brace/mode/sh" 4 | import "brace/mode/json" 5 | import "brace/theme/xcode" 6 | import "brace/theme/twilight" 7 | import "../Themes/dark/theme.css" 8 | import { BashWizardTheme } from '../Models/bwCommonModels'; 9 | 10 | 11 | interface IAceWrapperProps { 12 | readonly: boolean; 13 | theme: BashWizardTheme; 14 | value: string; 15 | focus?: boolean; 16 | mode: string; 17 | name: string; 18 | onChange?: (value: string, event?: any) => void; 19 | onBlur?: (event: any) => void; 20 | 21 | 22 | } 23 | 24 | export class AceWrapper extends React.PureComponent { 25 | constructor(props: IAceWrapperProps) { 26 | super(props); 27 | } 28 | 29 | public render() { 30 | const aceTheme = (this.props.theme === BashWizardTheme.Dark) ? "twilight" : "xcode"; 31 | return ( 32 | { 43 | this.setState({ value: newValue }) 44 | if (this.props.onChange !== undefined) { 45 | this.props.onChange(newValue, event); 46 | } 47 | }} 48 | onBlur={this.props.onBlur} 49 | /> 50 | ) 51 | } 52 | 53 | } 54 | export default AceWrapper; 55 | -------------------------------------------------------------------------------- /src/Themes/vott-theme.scss: -------------------------------------------------------------------------------- 1 | $color-yellow: #ffbb00; 2 | $color-green: #7cbb00; 3 | $color-blue: #00a1f1; 4 | $color-red: #f65314; 5 | 6 | $lighter-1: rgba(255,255,255,0.05); 7 | $lighter-2: rgba(255,255,255,0.10); 8 | $lighter-3: rgba(255,255,255,0.15); 9 | $lighter-4: rgba(255,255,255,0.20); 10 | $lighter-5: rgba(165, 165, 165, 1);; 11 | 12 | $darker-1: rgba(0,0,0,0.05); 13 | $darker-2: rgba(0,0,0,0.10); 14 | $darker-3: rgba(0,0,0,0.15); 15 | $darker-4: rgba(0,0,0,0.20); 16 | $darker-5: rgba(0,0,0,0.25); 17 | 18 | $main-bg-color: #353535; 19 | $main-fg-color: #a5a5a5; 20 | $main-border-color: #a5a5a5; 21 | $highlight-fg-color: #e5e5e5; 22 | $highlight-border-color: #e5e5e5; 23 | $toolbar-bg-color: #252525; 24 | $toolbar-border-color: #a5a5a5; 25 | $button-bg-color: #252525; 26 | $button-fg-color: #a5a5a5; 27 | $button-border-color: #252525; 28 | $button-hover-bg-color: #555555; 29 | $button-hover-fg-color: #e5e5e5; 30 | $button-hover-border-color: #e5e5e5; 31 | $button-selected-fg-color: #e5e5e5; 32 | $button-selected-bg-color: #454545; 33 | $button-selected-border-color: #e5e5e5; 34 | 35 | 36 | /* 37 | $lighter-1: rgba(165, 165, 165, 1); 38 | $lighter-2: rgba(165, 165, 165, 1); 39 | $lighter-3: rgba(165, 165, 165, 1); 40 | $lighter-4: rgba(165, 165, 165, 1); 41 | $lighter-5: rgba(229, 229, 229, 1); 42 | 43 | $darker-1:rgba(85, 85, 85, 1); 44 | $darker-2:rgba(69, 69, 69, 1); 45 | $darker-3:rgba(53, 53, 53, 1); 46 | $darker-4:rgba(37, 37, 37, 1); 47 | $darker-5:rgba(0, 0, 0, 1); 48 | */ 49 | 50 | 51 | .bg-darker-1 { 52 | background-color: $darker-1; 53 | } 54 | .bg-darker-2 { 55 | background-color: $darker-2; 56 | } 57 | .bg-darker-3 { 58 | background-color: $darker-3; 59 | } 60 | .bg-darker-4 { 61 | background-color: $darker-4; 62 | } 63 | .bg-darker-5 { 64 | background-color: $darker-5; 65 | } 66 | 67 | .bg-lighter-1 { 68 | background-color: $lighter-1; 69 | } 70 | .bg-lighter-2 { 71 | background-color: $lighter-2; 72 | } 73 | .bg-lighter-3 { 74 | background-color: $lighter-3; 75 | } 76 | .bg-lighter-4 { 77 | background-color: $lighter-4; 78 | } 79 | .bg-lighter-5 { 80 | background-color: $lighter-5; 81 | } 82 | -------------------------------------------------------------------------------- /src/Images/fileSaveAs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Images/parseJSON.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/types/old/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | declare module "frameless-titlebar2" { 5 | import React from "react" 6 | 7 | class TitleBar extends React.Component { 8 | setKeyById(id: string, key: string, value: any): void; 9 | getKeyById(id: string, key: string): MenuItem; 10 | } 11 | export default TitleBar; 12 | 13 | export interface IMenuTheme { 14 | barTheme: string; 15 | barHeight?: string; 16 | winBarHeightj?: string; 17 | barColor?: string; 18 | barTitleColor?: string; 19 | barBackgroundColor?: string; 20 | barShowBorder?: string; 21 | titleFontFamily?: string; 22 | titleFontWeight?: string; 23 | barBorderBottom?: string; 24 | // should the icon be shown in the center of the toolbar on Mac/Linux apps alongside the app or title property 25 | showIconDarLin?: boolean; 26 | 27 | /* Menu */ 28 | menuStyle?: 'horizontal' | 'vertical'; 29 | menuDimItems?: boolean; 30 | menuDimOpacity?: number; 31 | menuDisabledOpacity?: number; 32 | menuWidth?: number, 33 | menuBackgroundColor?: string; 34 | menuItemTextColor?: string; 35 | menuItemHoverBackground?: string; 36 | menuActiveTextColor?: string; 37 | menuTextHighlightColor?: string; 38 | menuHighlightColor?: string; 39 | accentStatusIcon?: boolean; 40 | menuSubLabelHeaders: true, 41 | menuSubLabelColor?: string; 42 | menuAcceleratorColor?: string; 43 | menuShowBoxShadow?: boolean; 44 | menuBoxShadow?: string; 45 | /* Menu Overlay */ 46 | menuOverlayBackground?: string; 47 | menuOverlayOpacity: number, 48 | menuSeparatorColor?: string; 49 | 50 | /* WindowControls */ 51 | windowControlsColor?: string; 52 | windowCloseHover?: string; 53 | windowCloseBackground?: string; 54 | windowCloseActive?: string; 55 | windowDefaultBackground?: string; 56 | windowDefaultActive?: string; 57 | } 58 | 59 | export interface MenuItem { 60 | id?: string; 61 | type?: "normal" | "separator" | "submenu" | "checkbox" | "radio"; 62 | label?: string; 63 | enabled?: string; 64 | visible?: boolean; 65 | sublabel?: string; 66 | accelerator?: string; 67 | icon?: ImageBitmapSource; 68 | checked?: boolean; 69 | submenu?: MenuItem[]; 70 | before?: string; 71 | after?: string; 72 | click?: (menuItem: MenuItem, browerWindow: any, e: Event) => void; 73 | 74 | } 75 | 76 | export interface TitleBarProps { 77 | icon?: string; 78 | app: string; 79 | title?: string; 80 | menu: MenuItem[]; 81 | theme?: IMenuTheme; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /ignore/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #---------- see https://github.com/joelong01/Bash-Wizard---------------- 3 | # bashWizard version 0.910 4 | # this will make the error text stand out in red - if you are looking at these errors/warnings in the log file 5 | # you can use cat to see the text in color. 6 | function echoError() { 7 | RED=$(tput setaf 1) 8 | NORMAL=$(tput sgr0) 9 | echo "${RED}${1}${NORMAL}" 10 | } 11 | function echoWarning() { 12 | YELLOW=$(tput setaf 3) 13 | NORMAL=$(tput sgr0) 14 | echo "${YELLOW}${1}${NORMAL}" 15 | } 16 | function echoInfo { 17 | GREEN=$(tput setaf 2) 18 | NORMAL=$(tput sgr0) 19 | echo "${GREEN}${1}${NORMAL}" 20 | } 21 | # make sure this version of *nix supports the right getopt 22 | ! getopt --test 2>/dev/null 23 | if [[ ${PIPESTATUS[0]} -ne 4 ]]; then 24 | echoError "'getopt --test' failed in this environment. please install getopt." 25 | read -r -p "install getopt using brew? [y,n]" response 26 | if [[ $response == 'y' ]] || [[ $response == 'Y' ]]; then 27 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null 28 | brew install gnu-getopt 29 | #shellcheck disable=SC2016 30 | echo 'export PATH="/usr/local/opt/gnu-getopt/bin:$PATH"' >> ~/.bash_profile 31 | echoWarning "you'll need to restart the shell instance to load the new path" 32 | fi 33 | exit 1 34 | fi 35 | 36 | function usage() { 37 | 38 | echo "abaddkdkdkdkdkdk" 39 | echo "" 40 | echo "Usage: $0 " 1>&2 41 | echo "" 42 | echo "" 43 | exit 1 44 | } 45 | function echoInput() { 46 | echo ":" 47 | 48 | } 49 | 50 | function parseInput() { 51 | 52 | local OPTIONS= 53 | local LONGOPTS= 54 | 55 | # -use ! and PIPESTATUS to get exit code with errexit set 56 | # -temporarily store output to be able to check for errors 57 | # -activate quoting/enhanced mode (e.g. by writing out "--options") 58 | # -pass arguments only via -- "$@" to separate them correctly 59 | ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") 60 | if [[ ${PIPESTATUS[0]} -ne 0 ]]; then 61 | # e.g. return value is 1 62 | # then getopt has complained about wrong arguments to stdout 63 | usage 64 | exit 2 65 | fi 66 | # read getopt's output this way to handle the quoting right: 67 | eval set -- "$PARSED" 68 | while true; do 69 | case "$1" in 70 | 71 | --) 72 | shift 73 | break 74 | ;; 75 | *) 76 | echoError "Invalid option $1 $2" 77 | exit 3 78 | ;; 79 | esac 80 | done 81 | } 82 | # input variables 83 | 84 | parseInput "$@" 85 | 86 | 87 | 88 | 89 | # --- BEGIN USER CODE --- 90 | 91 | # --- END USER CODE --- 92 | -------------------------------------------------------------------------------- /src/electron/ipcMainProxy.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, IpcMain } from "electron"; 2 | import { IpcProxyMessage } from "./ipcProxy"; 3 | 4 | export type IpcProxyHandler = (sender: any, args: T) => any; 5 | 6 | export class IpcMainProxy { 7 | private static PROXY_EVENT_NAME: string = "ipc-renderer-proxy"; 8 | 9 | public handlers: { [type: string]: IpcProxyHandler } = {}; 10 | 11 | constructor(private ipcMain: IpcMain, private browserWindow: BrowserWindow) { 12 | this.init(); 13 | } 14 | 15 | public register(type: string, handler: IpcProxyHandler) { 16 | this.handlers[type] = handler; 17 | } 18 | 19 | public registerProxy(proxyPrefix:any, provider:any) { 20 | Object.getOwnPropertyNames(provider.__proto__).forEach((memberName) => { 21 | // console.log(`registerProxy: [memberName=${memberName}] [proxyPrefix=${proxyPrefix}] [type=${typeof (provider[memberName])}]`) 22 | if (typeof (provider[memberName]) === "function") { 23 | this.register(`${proxyPrefix}:${memberName}`, (sender: any, eventArgs: any[]) => { 24 | return provider[memberName].apply(provider, eventArgs); 25 | }); 26 | } 27 | }); 28 | } 29 | 30 | private init() { 31 | this.ipcMain.on("ipc-main-proxy", (sender: any, message: IpcProxyMessage) => { 32 | const handler = this.handlers[message.type]; 33 | if (!handler) { 34 | console.log(`No IPC proxy handler defined for event type '${message.type}'`); 35 | } 36 | 37 | const returnArgs: IpcProxyMessage = { 38 | id: message.id, 39 | type: message.type, 40 | }; 41 | 42 | try { 43 | returnArgs.debug = JSON.stringify(message.args); 44 | 45 | const handlerValue = handler(sender, message.args); 46 | if (handlerValue && handlerValue.then) { 47 | handlerValue 48 | .then((result:any) => { 49 | returnArgs.result = result; 50 | this.browserWindow.webContents.send(IpcMainProxy.PROXY_EVENT_NAME, returnArgs); 51 | }) 52 | .catch((err:any) => { 53 | returnArgs.error = err; 54 | this.browserWindow.webContents.send(IpcMainProxy.PROXY_EVENT_NAME, returnArgs); 55 | }); 56 | } else { 57 | returnArgs.result = handlerValue; 58 | this.browserWindow.webContents.send(IpcMainProxy.PROXY_EVENT_NAME, returnArgs); 59 | } 60 | } catch (err) { 61 | returnArgs.error = err; 62 | this.browserWindow.webContents.send(IpcMainProxy.PROXY_EVENT_NAME, returnArgs); 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #---------- see https://github.com/joelong01/Bash-Wizard---------------- 3 | # bashWizard version 0.909 4 | # this will make the error text stand out in red - if you are looking at these errors/warnings in the log file 5 | # you can use cat to see the text in color. 6 | function echoError() { 7 | RED=$(tput setaf 1) 8 | NORMAL=$(tput sgr0) 9 | echo "${RED}${1}${NORMAL}" 10 | } 11 | function echoWarning() { 12 | YELLOW=$(tput setaf 3) 13 | NORMAL=$(tput sgr0) 14 | echo "${YELLOW}${1}${NORMAL}" 15 | } 16 | function echoInfo { 17 | GREEN=$(tput setaf 2) 18 | NORMAL=$(tput sgr0) 19 | echo "${GREEN}${1}${NORMAL}" 20 | } 21 | # make sure this version of *nix supports the right getopt 22 | ! getopt --test 2>/dev/null 23 | if [[ ${PIPESTATUS[0]} -ne 4 ]]; then 24 | echoError "'getopt --test' failed in this environment. please install getopt." 25 | read -r -p "install getopt using brew? [y,n]" response 26 | if [[ $response == 'y' ]] || [[ $response == 'Y' ]]; then 27 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null 28 | brew install gnu-getopt 29 | #shellcheck disable=SC2016 30 | echo 'export PATH="/usr/local/opt/gnu-getopt/bin:$PATH"' >> ~/.bash_profile 31 | echoWarning "you'll need to restart the shell instance to load the new path" 32 | fi 33 | exit 1 34 | fi 35 | 36 | function usage() { 37 | 38 | echo "" 39 | echo "" 40 | echo "Usage: $0 -- " 1>&2 41 | echo "" 42 | echo " -- Optional " 43 | echo "" 44 | exit 1 45 | } 46 | function echoInput() { 47 | echo ":" 48 | echo -n " .... " 49 | echoInfo "$" 50 | 51 | } 52 | 53 | function parseInput() { 54 | 55 | local OPTIONS= 56 | local LONGOPTS= 57 | 58 | # -use ! and PIPESTATUS to get exit code with errexit set 59 | # -temporarily store output to be able to check for errors 60 | # -activate quoting/enhanced mode (e.g. by writing out "--options") 61 | # -pass arguments only via -- "$@" to separate them correctly 62 | ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") 63 | if [[ ${PIPESTATUS[0]} -ne 0 ]]; then 64 | # e.g. return value is 1 65 | # then getopt has complained about wrong arguments to stdout 66 | usage 67 | exit 2 68 | fi 69 | # read getopt's output this way to handle the quoting right: 70 | eval set -- "$PARSED" 71 | while true; do 72 | case "$1" in 73 | - | --) 74 | = 75 | shift 1 76 | ;; 77 | --) 78 | shift 79 | break 80 | ;; 81 | *) 82 | echoError "Invalid option $1 $2" 83 | exit 3 84 | ;; 85 | esac 86 | done 87 | } 88 | # input variables 89 | declare = 90 | 91 | parseInput "$@" 92 | 93 | 94 | 95 | 96 | # --- BEGIN USER CODE --- 97 | 98 | # --- END USER CODE --- 99 | -------------------------------------------------------------------------------- /src/Themes/titleBar-light.scss: -------------------------------------------------------------------------------- 1 | $color-yellow: #ffbb00; 2 | $color-green: #7cbb00; 3 | $color-blue: #00a1f1; 4 | $color-red: #f65314; 5 | 6 | $lighter-1: rgba(255,255,255,0.05); 7 | $lighter-2: rgba(255,255,255,0.10); 8 | $lighter-3: rgba(255,255,255,0.15); 9 | $lighter-4: rgba(255,255,255,0.20); 10 | $lighter-5: rgba(165, 165, 165, 1);; 11 | 12 | $darker-1: rgba(0,0,0,0.05); 13 | $darker-2: rgba(0,0,0,0.10); 14 | $darker-3: rgba(0,0,0,0.15); 15 | $darker-4: rgba(0,0,0,0.20); 16 | $darker-5: rgba(0,0,0,0.25); 17 | 18 | $main-bg-color: white; 19 | $main-fg-color: black; 20 | $main-border-color: black; 21 | $highlight-fg-color: lightblue; 22 | $highlight-border-color: darkblue; 23 | $toolbar-bg-color: lightgray; 24 | $toolbar-border-color: darkgray; 25 | $button-bg-color: lightgray; 26 | $button-fg-color: black; 27 | $button-border-color: blue; 28 | $button-hover-bg-color: darkgray; 29 | $button-hover-fg-color: white; 30 | $button-hover-border-color: blue; 31 | $button-selected-fg-color: red; 32 | $button-selected-bg-color: yellow; 33 | $button-selected-border-color: purple; 34 | 35 | 36 | .title-bar { 37 | color: $main-fg-color; 38 | background-color:$main-bg-color; 39 | 40 | &-icon svg { 41 | fill: $main-fg-color; 42 | } 43 | 44 | &-controls { 45 | li { 46 | color:$main-fg-color; 47 | 48 | 49 | &:hover { 50 | color: #fff; 51 | background-color: $button-hover-bg-color; 52 | 53 | } 54 | } 55 | 56 | .btn-window-close { 57 | &:hover { 58 | background-color: $color-red; 59 | color: #fff; 60 | } 61 | } 62 | } 63 | } 64 | 65 | .menu-item { 66 | 67 | &-accelerator { 68 | color: $button-fg-color; 69 | text-align: right; 70 | margin-left: 2em; 71 | } 72 | 73 | } 74 | 75 | .rc-menu { 76 | color:$main-fg-color; 77 | 78 | 79 | &-item-group-title { 80 | color: $button-fg-color; 81 | } 82 | 83 | &-item-active, 84 | &-submenu-active > &-submenu-title { 85 | background-color: $button-hover-bg-color; 86 | color: $button-hover-fg-color; 87 | } 88 | 89 | &-item-selected { 90 | background-color: red; 91 | } 92 | 93 | &-submenu-selected { 94 | background-color:red; 95 | } 96 | 97 | 98 | &-item, &-submenu-title { 99 | // Disabled state sets text to gray and nukes hover/tab effects 100 | &.rc-menu-item-disabled, &.rc-menu-submenu-disabled { 101 | color: #777 !important; 102 | } 103 | } 104 | & > &-item-divider { 105 | border-top: 1px solid $main-fg-color; 106 | border-bottom: 1px solid $main-fg-color; 107 | } 108 | 109 | &-submenu { 110 | &-popup { 111 | background-color: $main-bg-color; 112 | border: 1px solid $toolbar-bg-color; 113 | box-shadow: 0px 2px 5px $toolbar-bg-color; 114 | } 115 | > .rc-menu { 116 | background-color: $main-bg-color; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Themes/titleBar-dark.scss: -------------------------------------------------------------------------------- 1 | $color-yellow: #ffbb00; 2 | $color-green: #7cbb00; 3 | $color-blue: #00a1f1; 4 | $color-red: #f65314; 5 | 6 | $lighter-1: rgba(255,255,255,0.05); 7 | $lighter-2: rgba(255,255,255,0.10); 8 | $lighter-3: rgba(255,255,255,0.15); 9 | $lighter-4: rgba(255,255,255,0.20); 10 | $lighter-5: rgba(165, 165, 165, 1);; 11 | 12 | $darker-1: rgba(0,0,0,0.05); 13 | $darker-2: rgba(0,0,0,0.10); 14 | $darker-3: rgba(0,0,0,0.15); 15 | $darker-4: rgba(0,0,0,0.20); 16 | $darker-5: rgba(0,0,0,0.25); 17 | 18 | $main-bg-color: #353535; 19 | $main-fg-color: #a5a5a5; 20 | $main-border-color: #a5a5a5; 21 | $highlight-fg-color: #e5e5e5; 22 | $highlight-border-color: #e5e5e5; 23 | $toolbar-bg-color: #252525; 24 | $toolbar-border-color: #a5a5a5; 25 | $button-bg-color: #252525; 26 | $button-fg-color: #a5a5a5; 27 | $button-border-color: #252525; 28 | $button-hover-bg-color: #555555; 29 | $button-hover-fg-color: #e5e5e5; 30 | $button-hover-border-color: #e5e5e5; 31 | $button-selected-fg-color: #e5e5e5; 32 | $button-selected-bg-color: #454545; 33 | $button-selected-border-color: #e5e5e5; 34 | 35 | 36 | body.dark .title-bar { 37 | color: $main-fg-color; 38 | background-color:$main-bg-color; 39 | 40 | 41 | 42 | &-controls { 43 | 44 | 45 | li { 46 | color:$main-fg-color; 47 | 48 | 49 | &:hover { 50 | color: #fff; 51 | background-color: $button-hover-bg-color; 52 | 53 | } 54 | } 55 | 56 | .btn-window-close { 57 | &:hover { 58 | background-color: $color-red; 59 | color: #fff; 60 | } 61 | } 62 | } 63 | } 64 | 65 | body.dark .menu-item { 66 | 67 | &-accelerator { 68 | color: $button-fg-color; 69 | text-align: right; 70 | margin-left: 2em; 71 | } 72 | 73 | } 74 | 75 | body.dark .rc-menu { 76 | color:$main-fg-color; 77 | 78 | 79 | &-item-group-title { 80 | color: $button-fg-color; 81 | } 82 | 83 | &-item-active, 84 | &-submenu-active > &-submenu-title { 85 | background-color: $button-hover-bg-color; 86 | color: $button-hover-fg-color; 87 | } 88 | 89 | &-item-selected { 90 | background-color: red; 91 | } 92 | 93 | &-submenu-selected { 94 | background-color:red; 95 | } 96 | 97 | 98 | &-item, &-submenu-title { 99 | // Disabled state sets text to gray and nukes hover/tab effects 100 | &.rc-menu-item-disabled, &.rc-menu-submenu-disabled { 101 | color: #777 !important; 102 | } 103 | } 104 | & > &-item-divider { 105 | border-top: 2px solid red; 106 | border-bottom: 2px solid red; 107 | } 108 | 109 | &-submenu { 110 | &-popup { 111 | background-color: $main-bg-color; 112 | border: 1px solid $toolbar-bg-color; 113 | box-shadow: 0px 2px 5px $toolbar-bg-color; 114 | } 115 | > .rc-menu { 116 | background-color: $main-bg-color; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/electron/mainServiceProxy.ts: -------------------------------------------------------------------------------- 1 | import { IpcRendererProxy } from "../ipcRendererProxy"; 2 | import { IBashWizardMainService, IBashWizardSettings } from "../Models/bwCommonModels" 3 | import { FileFilter } from 'electron'; 4 | 5 | const PROXY_NAME = "BashWizardMainService"; 6 | 7 | 8 | /** 9 | * Storage Provider for Local File System. Only available in Electron application 10 | * Leverages the IpcRendererProxy 11 | */ 12 | export class BashWizardMainServiceProxy implements IBashWizardMainService { 13 | /** 14 | * Uses the FileOpen dialog to find a file to open 15 | * @param title - Title of the dialog 16 | * @param extensions - FileFilter array for the dialog 17 | */ 18 | public getOpenFile(title: string, extensions: FileFilter[]): Promise { 19 | return IpcRendererProxy.send(`${PROXY_NAME}:getOpenFile`, [ 20 | title, 21 | extensions 22 | ]); 23 | } 24 | /** 25 | * Uses the FileSave dialog to find a file to save 26 | * @param title - Title of the dialog 27 | * @param extensions - FileFilter array for the dialog 28 | */ 29 | public getSaveFile(title: string, extensions: FileFilter[]): Promise { 30 | return IpcRendererProxy.send(`${PROXY_NAME}:getSaveFile`, [ 31 | title, 32 | extensions 33 | ]); 34 | } 35 | 36 | /** 37 | * Read text from file 38 | * @param fileName - Name of file from which to read text 39 | */ 40 | public readText(fileName: string): Promise { 41 | const filePath = [fileName].join("/"); 42 | return IpcRendererProxy.send(`${PROXY_NAME}:readText`, [filePath]); 43 | } 44 | 45 | 46 | /** 47 | * Write text to file 48 | * @param fileName Name of target file 49 | * @param contents Contents to be written 50 | */ 51 | public writeText(fileName: string, contents: string): Promise { 52 | const filePath = [fileName].join("/"); 53 | return IpcRendererProxy.send(`${PROXY_NAME}:writeText`, [ 54 | filePath, 55 | contents 56 | ]); 57 | } 58 | /** 59 | * reads the file "bashWizardConfig.json" from the same directory as the .EXE and returns their values 60 | */ 61 | getAndApplySettings(): Promise { 62 | return IpcRendererProxy.send(`${PROXY_NAME}:getAndApplySettings`); 63 | 64 | } 65 | /** 66 | * saves the file "bashWizardConfig.json" in the same directory as the .EXE and returns their values 67 | */ 68 | saveAndApplySettings(settings: IBashWizardSettings): Promise { 69 | return IpcRendererProxy.send(`${PROXY_NAME}:saveAndApplySettings`, [settings]) 70 | 71 | } 72 | 73 | /** 74 | * saves the file "bashWizardConfig.json" in the same directory as the .EXE and returns their values 75 | */ 76 | updateSetting(setting: Partial): Promise { 77 | return IpcRendererProxy.send(`${PROXY_NAME}:updateSetting`, [setting]); 78 | 79 | } 80 | 81 | 82 | /** 83 | * saves the file "bashWizardConfig.json" in the same directory as the .EXE and returns their values 84 | */ 85 | setWindowTitle(name:string): Promise { 86 | return IpcRendererProxy.send(`${PROXY_NAME}:setWindowTitle`, [name]); 87 | 88 | } 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/Components/askUserYesNoDlg.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dialog } from 'primereact/dialog'; 3 | import { Button } from 'primereact/button'; 4 | 5 | export enum YesNo { 6 | Yes, 7 | No 8 | } 9 | 10 | export interface IYesNoResponse{ 11 | answer:YesNo; 12 | neverAsk?:boolean; 13 | } 14 | 15 | export type YesNoResponse = (response: IYesNoResponse) => void; 16 | 17 | interface IYesNoDialogProps { 18 | message?: string; 19 | visible?: boolean; 20 | Notify?: YesNoResponse; 21 | 22 | } 23 | 24 | interface IYesNoDialogState { 25 | message?: string; 26 | Notify?: YesNoResponse; 27 | dialogAnswer: "yes" | "no"; 28 | visible: boolean; 29 | neverAsk?: boolean; 30 | showYesAlways: boolean; 31 | } 32 | 33 | export class YesNoDialog extends React.PureComponent { 34 | 35 | constructor(props: IYesNoDialogProps) { 36 | super(props); 37 | this.state = { 38 | message: this.props.message, 39 | Notify: this.props.Notify, 40 | visible: false, 41 | dialogAnswer: "no", 42 | showYesAlways: false, 43 | neverAsk: undefined, 44 | }; 45 | } 46 | public waitForDlgAnswer = async (msg: string, showCB: boolean): Promise => { 47 | return new Promise((resolve, reject) => { 48 | this.setState({ visible: true, message: msg, showYesAlways: showCB }, () => { 49 | this.setState({ 50 | Notify: (response: IYesNoResponse) => { 51 | this.setState({ visible: false }, () => { 52 | resolve(response); 53 | }); 54 | } 55 | }); 56 | }); 57 | }); 58 | } 59 | private dlgOnYes = async () => { 60 | if (this.state.Notify !== undefined) { 61 | this.state.Notify({answer: YesNo.Yes, neverAsk: false}); 62 | } 63 | } 64 | private dlgOnYesAlways = async () => { 65 | if (this.state.Notify !== undefined) { 66 | this.state.Notify({answer: YesNo.Yes, neverAsk: true}); 67 | } 68 | } 69 | private dlgOnNo = async () => { 70 | if (this.state.Notify !== undefined) { 71 | this.state.Notify({answer: YesNo.No, neverAsk: false}); 72 | } 73 | } 74 | 75 | private dlgClosed = async () => { 76 | return this.dlgOnNo(); 77 | } 78 | public render = () => { 79 | const dialogFooter = ( 80 |
81 | 82 |
); 91 | 92 | return ( 93 |
94 | 95 | {this.state.message === "" ? "" : this.state.message} 96 | 97 |
98 | ); 99 | } 100 | 101 | } 102 | 103 | export default YesNoDialog; 104 | -------------------------------------------------------------------------------- /src/Images/vsCodeDebugInfo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bashwizard", 3 | "productName": "BashWizard", 4 | "version": "1.0.0", 5 | "private": true, 6 | "main": "./build/main.js", 7 | "homepage": ".", 8 | "dependencies": { 9 | "@babel/runtime": "^7.5.5", 10 | "@fortawesome/fontawesome-free": "^5.10.1", 11 | "@types/lodash-es": "^4.17.3", 12 | "@types/shortid": "0.0.29", 13 | "bash-models": "^1.1.12", 14 | "classnames": "^2.2.6", 15 | "electron-window-state": "^5.0.3", 16 | "fs": "0.0.1-security", 17 | "jquery": "^3.5.0", 18 | "lodash-es": "^4.17.15", 19 | "njv": "^2.0.0", 20 | "popper.js": "^1.15.0", 21 | "primeflex": "^1.0.0-rc.1", 22 | "primeicons": "^1.0.0", 23 | "primereact": "^3.1.8", 24 | "rc-menu": "^7.4.23", 25 | "react": "^16.9.0", 26 | "react-ace": "^6.6.0", 27 | "react-dom": "^16.9.0", 28 | "react-split-pane": "^0.1.87", 29 | "react-splitter-layout": "^3.0.1", 30 | "react-svg": "^7.2.14", 31 | "react-transition-group": "^2.6.1", 32 | "shortid": "^2.2.14", 33 | "strongly-typed-events": "^1.6.2", 34 | "universal-cookie": "^3.1.0" 35 | }, 36 | "eslintConfig": { 37 | "extends": "react-app" 38 | }, 39 | "scripts": { 40 | "clean": "rimraf ./dist && rimraf ./build && rimraf ./releases", 41 | "start": "nf start -p 3000", 42 | "compile": "tsc", 43 | "build": "npm run clean && react-scripts build", 44 | "webpack:dev": "webpack --config ./config/webpack.dev.js", 45 | "webpack:prod": "webpack -p --config ./config/webpack.prod.js", 46 | "react-start": "npm run clean && react-scripts start", 47 | "electron:run:dev": "npm run webpack:dev && electron . --remote-debugging-port=9223", 48 | "electron:run:prod": "npm run webpack:prod && electron . --remote-debugging-port=9223", 49 | "electron:start:dev": "npm run webpack:dev && npm run electron-start", 50 | "electron:start:prod": "npm run webpack:prod && npm run electron-start", 51 | "electron-start": "node src/electron/start", 52 | "eject": "react-scripts eject", 53 | "release": "npm run build && npm run webpack:prod && electron-builder", 54 | "pretest": "./node_modules/.bin/tslint 'src/**/*.ts*'", 55 | "lintfix": "./node_modules/.bin/tslint 'src/**/*.ts*' --fix", 56 | "test": "react-scripts test --env=jsdom --silent", 57 | "test:ci": "cross-env CI=true npm run test", 58 | "test:coverage": "npm run test -- --coverage", 59 | "plato": "scripts/generate-report.sh -o report -v $(node -pe \"require('./package.json').version\") -c $(git rev-parse --short HEAD)", 60 | "postinstall": "electron-builder install-app-deps", 61 | "predebug": "npm run build && npm run webpack:dev" 62 | }, 63 | "devDependencies": { 64 | "@types/jest": "^23.3.14", 65 | "@types/node": "^10.14.15", 66 | "@types/react": "^16.9.1", 67 | "@types/react-dom": "^16.8.5", 68 | "electron": "^25.3.1", 69 | "electron-builder": "^24.4.0", 70 | "electron-packager": "^13.1.1", 71 | "foreman": "^3.0.1", 72 | "node-sass": "^9.0.0", 73 | "react-scripts": "^5.0.1", 74 | "rimraf": "^2.6.3", 75 | "ts-loader": "^5.4.5", 76 | "tslint": "^5.18.0", 77 | "tslint-config-prettier": "^1.18.0", 78 | "tslint-react": "^4.0.0", 79 | "typescript": "^3.5.3", 80 | "webpack": "^4.39.1", 81 | "webpack-cli": "^3.3.12", 82 | "webpack-merge": "^4.1.5" 83 | }, 84 | "browserslist": [ 85 | ">0.2%", 86 | "not dead", 87 | "not ie <= 11", 88 | "not op_mini all" 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /src/Pages/MainPage.css: -------------------------------------------------------------------------------- 1 | /* trying to set the fonts consistently everywhere. p-component is a shared class with prime react, so this will set 2 | all components to use these fonts. the internal components are set to the same font in ./index.css */ 3 | 4 | .p-component { 5 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif !important; 6 | } 7 | .fa-file-medical { 8 | padding-left: 0.5em; 9 | } 10 | .p-button-secondary{ 11 | font-size: 1em; 12 | } 13 | 14 | .mode-toggle-button { 15 | font-size: 1em; 16 | font-weight: normal; 17 | margin-top: .5em; 18 | border-width: 1px; 19 | } 20 | 21 | .DIV_Top { 22 | width: 100%; 23 | margin: 0em; 24 | position: relative; 25 | background-color: transparent; 26 | 27 | } 28 | .top-non-titlebar{ 29 | margin: 0em .25em .25em .25em; 30 | width: calc( 100% - .5em); 31 | height: calc(100% - 34px); 32 | } 33 | 34 | .Parameter_List { 35 | overflow: auto; 36 | position: relative; 37 | margin: 0em; 38 | flex: 1; 39 | /* height: calc(100% - 115px); Height calculated in this.handleResize() and set as style in code*/ 40 | } 41 | 42 | 43 | /* the div that holds the form for entering description and scriptname */ 44 | 45 | .DIV_globalEntry { 46 | position: relative; 47 | width: 100%; 48 | top: 0.5em; 49 | height: 4.25em; 50 | } 51 | 52 | .column-global-entry { 53 | width: 33%; 54 | } 55 | .autoinstallLabel { 56 | margin-left: 1.5em !important; 57 | } 58 | .grid-global-entry { 59 | padding-top: 0.5em; 60 | } 61 | 62 | .tabControl { 63 | /* the Tabs inherit these font settings */ 64 | position: absolute; 65 | font-size: 1em; 66 | font-weight: bolder; 67 | height: 100%; 68 | width: 100%; 69 | margin: 0em; 70 | } 71 | 72 | .p-tabview-panels { 73 | background-color: lightgray; 74 | height: calc(100% - 40px); 75 | } 76 | 77 | .param-label { 78 | margin-left: 0px !important; 79 | font-style: italic; 80 | } 81 | 82 | /* the Ace Editors */ 83 | 84 | .bw-ace { 85 | width: 100% !important; 86 | height: 100% !important; 87 | } 88 | 89 | .divEditor { 90 | position: absolute; 91 | width: calc(100% - 1em); 92 | height: calc(100% - 4em); 93 | top: 3.25em; 94 | left: 0.5em; 95 | border-radius: 3px; 96 | } 97 | 98 | 99 | 100 | 101 | 102 | /* this is the little dot next to the light/dark mode. 103 | it makes the circle dark when in dark mode */ 104 | 105 | .pi-circle-on { 106 | color: black !important; 107 | } 108 | 109 | /* the New Button on the Toolbar */ 110 | .bw-button > div { 111 | display: inline-block; 112 | } 113 | .bw-button { 114 | margin-top: 0px; 115 | color: #333333; 116 | background-color: #f4f4f4; 117 | border: 1px solid #f4f4f4; 118 | vertical-align: middle; 119 | transition: background-color 0.2s, box-shadow 0.2s; 120 | border-radius: 3px; 121 | position: relative; 122 | box-sizing: unset; 123 | padding: 0; 124 | text-decoration: none !important; 125 | cursor: pointer; 126 | text-align: center; 127 | width: 7.5em; 128 | height: 2.5em; 129 | margin-right: 0.25em; 130 | } 131 | 132 | /* toolbar button */ 133 | 134 | .bw-button:hover { 135 | background-color: #c8c8c8; 136 | color: #333333; 137 | border-color: #c8c8c8; 138 | border-radius: 0.25em; 139 | } 140 | 141 | .bw-button-span { 142 | position: relative; 143 | font-size: 1em; 144 | font-weight: normal; 145 | padding-left: .25em; 146 | line-height: 1em; 147 | 148 | } 149 | .svg-file-new { 150 | margin-top: 0.125em; 151 | } 152 | 153 | .svg-file-new svg { 154 | position: relative; 155 | height: 1.0em; 156 | width: 1.0em; 157 | 158 | 159 | 160 | } 161 | 162 | .svg-file-new path { 163 | stroke: #333333;; 164 | fill: #333333;; 165 | stroke-width: 0.75px; 166 | } 167 | 168 | .p-menuitem-link[target="separator"] { 169 | height: 0px; 170 | display: block; 171 | padding: 0; 172 | min-height: 0; 173 | border-bottom: 1px #525252 solid; 174 | padding: 0em !important; 175 | } 176 | 177 | /* splitter */ 178 | 179 | .Resizer.horizontal { 180 | height: 0.5em; 181 | background-color: lightgray; 182 | border: 0.125em solid darkgray; 183 | cursor: row-resize; 184 | transition: background-color 0.2s, box-shadow 0.2s; 185 | width: 100%; 186 | } 187 | 188 | .Resizer.horizontal:hover { 189 | background-color: darkgray; 190 | } 191 | 192 | .Resizer.disabled { 193 | cursor: not-allowed; 194 | } 195 | 196 | .Resizer.disabled:hover { 197 | border-color: transparent; 198 | } 199 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | // In production, we register a service worker to serve assets from local cache. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on the 'N+1' visit to a page, since previously 7 | // cached resources are updated in the background. 8 | 9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 10 | // This link also includes instructions on opting out of this behavior. 11 | 12 | const isLocalhost = Boolean( 13 | window.location.hostname === 'localhost' || 14 | // [::1] is the IPv6 localhost address. 15 | window.location.hostname === '[::1]' || 16 | // 127.0.0.1/8 is considered localhost for IPv4. 17 | window.location.hostname.match( 18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 19 | ) 20 | ); 21 | 22 | export default function register() { 23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 24 | // The URL constructor is available in all browsers that support SW. 25 | const publicUrl = new URL( 26 | process.env.PUBLIC_URL!, 27 | window.location.toString() 28 | ); 29 | if (publicUrl.origin !== window.location.origin) { 30 | // Our service worker won't work if PUBLIC_URL is on a different origin 31 | // from what our page is served on. This might happen if a CDN is used to 32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 33 | return; 34 | } 35 | 36 | window.addEventListener('load', () => { 37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 38 | 39 | if (isLocalhost) { 40 | // This is running on localhost. Lets check if a service worker still exists or not. 41 | checkValidServiceWorker(swUrl); 42 | 43 | // Add some additional logging to localhost, pointing developers to the 44 | // service worker/PWA documentation. 45 | navigator.serviceWorker.ready.then(() => { 46 | console.log( 47 | 'This web app is being served cache-first by a service ' + 48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 49 | ); 50 | }); 51 | } else { 52 | // Is not local host. Just register service worker 53 | registerValidSW(swUrl); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | function registerValidSW(swUrl: string) { 60 | navigator.serviceWorker 61 | .register(swUrl) 62 | .then(registration => { 63 | registration.onupdatefound = () => { 64 | const installingWorker = registration.installing; 65 | if (installingWorker) { 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the old content will have been purged and 70 | // the fresh content will have been added to the cache. 71 | // It's the perfect time to display a 'New content is 72 | // available; please refresh.' message in your web app. 73 | console.log('New content is available; please refresh.'); 74 | } else { 75 | // At this point, everything has been precached. 76 | // It's the perfect time to display a 77 | // 'Content is cached for offline use.' message. 78 | console.log('Content is cached for offline use.'); 79 | } 80 | } 81 | }; 82 | } 83 | }; 84 | }) 85 | .catch(error => { 86 | console.error('Error during service worker registration:', error); 87 | }); 88 | } 89 | 90 | function checkValidServiceWorker(swUrl: string) { 91 | // Check if the service worker can be found. If it can't reload the page. 92 | fetch(swUrl) 93 | .then(response => { 94 | // Ensure service worker exists, and that we really are getting a JS file. 95 | if ( 96 | response.status === 404 || 97 | response.headers.get('content-type')!.indexOf('javascript') === -1 98 | ) { 99 | // No service worker found. Probably a different app. Reload the page. 100 | navigator.serviceWorker.ready.then(registration => { 101 | registration.unregister().then(() => { 102 | window.location.reload(); 103 | }); 104 | }); 105 | } else { 106 | // Service worker found. Proceed as normal. 107 | registerValidSW(swUrl); 108 | } 109 | }) 110 | .catch(() => { 111 | console.log( 112 | 'No internet connection found. App is running in offline mode.' 113 | ); 114 | }); 115 | } 116 | 117 | export function unregister() { 118 | if ('serviceWorker' in navigator) { 119 | navigator.serviceWorker.ready.then(registration => { 120 | registration.unregister(); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Components/globalScriptState.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { InputText } from "primereact/inputtext" 3 | import { Checkbox } from "primereact/checkbox" 4 | 5 | 6 | interface IGlobalScriptState { 7 | autoInstallDependencies:boolean; 8 | description: string; 9 | scriptName: string; 10 | onBlur?: (key: "Name" | "Description", value: string) => void; 11 | onCheckedAutoInstall?: (checked:boolean) => void; 12 | } 13 | 14 | 15 | 16 | 17 | export class GlobalScriptData extends React.PureComponent { 18 | constructor(props: IGlobalScriptState) { 19 | super(props); 20 | this.state = { 21 | autoInstallDependencies: this.props.autoInstallDependencies, 22 | description: this.props.description, 23 | scriptName: this.props.scriptName, 24 | onBlur: this.props.onBlur, 25 | onCheckedAutoInstall: this.props.onCheckedAutoInstall 26 | } 27 | } 28 | 29 | 30 | get Description(): string { 31 | return this.state.description; 32 | } 33 | 34 | set Description(value: string) { 35 | if (value !== this.state.description) { 36 | 37 | this.setState({ description: value }) 38 | 39 | } 40 | } 41 | 42 | get ScriptName(): string { 43 | return this.state.scriptName; 44 | } 45 | 46 | set ScriptName(value: string) { 47 | if (value !== this.state.scriptName) { 48 | 49 | this.setState({ scriptName: value }) 50 | 51 | } 52 | } 53 | 54 | get AutoInstallDependencies(): boolean { 55 | return this.state.autoInstallDependencies; 56 | } 57 | 58 | set AutoInstallDependencies(value: boolean) { 59 | if (value !== this.state.autoInstallDependencies) { 60 | 61 | this.setState({ autoInstallDependencies: value }) 62 | 63 | } 64 | } 65 | 66 | 67 | public render = () => { 68 | return ( 69 |
70 |
71 |
72 | 73 | ) => { 78 | this.setState({ scriptName: e.currentTarget.value }); 79 | }} 80 | onBlur={() => { 81 | if (this.state.onBlur !== undefined) { 82 | this.state.onBlur("Name", this.state.scriptName); 83 | } 84 | }} /> 85 | 86 | 87 |
88 |
89 | 90 | ) => { 96 | this.setState({ description: e.currentTarget.value }); 97 | }} 98 | onBlur={() => { 99 | if (this.state.onBlur !== undefined) { 100 | this.state.onBlur("Description", this.state.description); 101 | } 102 | }} /> 103 | 105 | 106 |
107 | 108 |
109 | 110 | { 117 | this.setState({ autoInstallDependencies: e.checked }); 118 | if (this.state.onCheckedAutoInstall !== undefined) { 119 | this.state.onCheckedAutoInstall(e.checked) 120 | } 121 | }} 122 | /> 123 | 126 | 127 |
128 |
129 |
130 | 131 | ); 132 | } 133 | } 134 | 135 | export default GlobalScriptData; 136 | -------------------------------------------------------------------------------- /src/electron/bwMainService.ts: -------------------------------------------------------------------------------- 1 | import electron, { BrowserWindow, dialog, FileFilter, Menu } from "electron"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | import { IBashWizardMainService, IBashWizardSettings, BashWizardTheme } from "../Models/bwCommonModels"; 5 | 6 | 7 | 8 | 9 | 10 | export class BashWizardMainService implements IBashWizardMainService { 11 | private myBrowserWindow: BrowserWindow; 12 | constructor(private browserWindow: BrowserWindow) { 13 | this.myBrowserWindow = browserWindow; 14 | } 15 | public setWindowTitle(name: string): Promise { 16 | return new Promise((resolve, reject) => { 17 | console.log("setting window title to: " + name) 18 | this.myBrowserWindow.setTitle("Bash Wizard: " + name); 19 | resolve(); 20 | }); 21 | 22 | } 23 | public getOpenFile(dlgTitle: string, exts: FileFilter[]): Promise { 24 | return new Promise((resolve, reject) => { 25 | const fileNames = dialog.showOpenDialog({ 26 | title: dlgTitle, 27 | filters: exts 28 | }); 29 | 30 | if (fileNames === undefined || fileNames.length !== 1) { 31 | return reject; 32 | } 33 | 34 | return resolve(fileNames[0]); 35 | }); 36 | } 37 | 38 | public selectDirectory(dlgTitle: string, exts: FileFilter[]): Promise { 39 | return new Promise((resolve, reject) => { 40 | const dirs = dialog.showOpenDialog({ 41 | properties: ['openDirectory'], 42 | title: dlgTitle, 43 | }); 44 | 45 | if (dirs === undefined ) { 46 | return reject; 47 | } 48 | 49 | return resolve(dirs[0]); 50 | }); 51 | } 52 | 53 | 54 | public getSaveFile(dlgTitle: string, exts: FileFilter[]): Promise { 55 | return new Promise((resolve, reject) => { 56 | const fileName: string | undefined = dialog.showSaveDialog({ 57 | title: dlgTitle, 58 | filters: exts 59 | }); 60 | 61 | if (fileName === undefined) { 62 | return reject; 63 | } 64 | console.log(`getSaveFile: ${fileName}`) 65 | return resolve(fileName); 66 | }); 67 | } 68 | 69 | public readText(filePath: string): Promise { 70 | return new Promise((resolve, reject) => { 71 | fs.readFile( 72 | path.normalize(filePath), 73 | (err: NodeJS.ErrnoException, data: Buffer) => { 74 | if (err) { 75 | return reject(err); 76 | } 77 | this.setWindowTitle(filePath); 78 | resolve(data.toString()); 79 | } 80 | ); 81 | }); 82 | } 83 | 84 | public writeText(filePath: string, contents: string): Promise { 85 | this.setWindowTitle(filePath); 86 | const buffer = Buffer.alloc(contents.length, contents); 87 | return new Promise((resolve, reject) => { 88 | try { 89 | const containerName: fs.PathLike = path.normalize( 90 | path.dirname(filePath) 91 | ); 92 | const exists = fs.existsSync(containerName); 93 | if (!exists) { 94 | fs.mkdirSync(containerName); 95 | } 96 | 97 | fs.writeFile(path.normalize(filePath), buffer, err => { 98 | if (err) { 99 | return reject(err); 100 | } 101 | 102 | resolve(); 103 | }); 104 | } 105 | catch (e) { 106 | reject(); 107 | } 108 | }); 109 | } 110 | private getSettingFileName(): string { 111 | let localDir: string = "./" 112 | try { 113 | 114 | localDir = (electron.app || electron.remote.app).getPath('userData'); 115 | } 116 | catch{ 117 | console.log("couldn't get localDir"); 118 | } 119 | const fileName: string = path.join(localDir + "\\BashWizardSettings.json"); 120 | console.log(`[config file = ${fileName}]`); 121 | return fileName; 122 | } 123 | private getDefaultSettings(): IBashWizardSettings { 124 | const settings: IBashWizardSettings = { 125 | autoSave: false, 126 | theme: BashWizardTheme.Light, 127 | autoUpdate: false, 128 | showDebugger: false 129 | } 130 | return settings; 131 | 132 | } 133 | public getAndApplySettings(): Promise { 134 | let settings: IBashWizardSettings; 135 | const settingsFile = this.getSettingFileName(); 136 | return new Promise(async (resolve, reject) => { 137 | try { 138 | 139 | console.log("service is getting settings") 140 | const contents: string = fs.readFileSync(settingsFile).toString(); 141 | settings = JSON.parse(contents); 142 | return resolve(settings); 143 | } 144 | catch (error) { 145 | console.log("getAndApplySettings error %s", error); 146 | settings = this.getDefaultSettings(); 147 | return resolve(settings); // we never pass the exception back to the client 148 | } 149 | finally { 150 | fs.writeFileSync(settingsFile, JSON.stringify(settings)); 151 | this.applySettings(settings); 152 | } 153 | }); 154 | } 155 | 156 | public saveAndApplySettings(settings: IBashWizardSettings): Promise { 157 | return new Promise((resolve, reject) => { 158 | try { 159 | console.log(`saving settings: ${JSON.stringify(settings)}`) 160 | this.applySettings(settings); 161 | fs.writeFileSync(this.getSettingFileName(), JSON.stringify(settings)); 162 | resolve(); 163 | } 164 | catch (e) { 165 | reject(e); 166 | } 167 | }); 168 | } 169 | 170 | public updateSetting(setting: Partial): Promise { 171 | return new Promise(async (resolve, reject) => { 172 | try { 173 | console.log(`updating setting: ${JSON.stringify(setting)}`); 174 | const settings: IBashWizardSettings = await this.getAndApplySettings() 175 | const newStettings = Object.assign(settings, setting); 176 | fs.writeFileSync(this.getSettingFileName(), JSON.stringify(newStettings)); 177 | resolve(); 178 | } 179 | catch (e) { 180 | reject(e); 181 | } 182 | }); 183 | } 184 | 185 | private applySettings(settings: IBashWizardSettings): void { 186 | if (settings === undefined) { 187 | return; 188 | } 189 | 190 | const menu: Menu | null = Menu.getApplicationMenu(); 191 | if (menu === null) { 192 | throw new Error("No menu in react app!"); 193 | } 194 | 195 | menu.getMenuItemById("auto-save").checked = settings.autoSave; 196 | menu.getMenuItemById("auto-load").checked = settings.autoUpdate; 197 | 198 | } 199 | 200 | 201 | } 202 | 203 | export default BashWizardMainService; 204 | -------------------------------------------------------------------------------- /src/Components/titleBar.scss: -------------------------------------------------------------------------------- 1 | @import '../Themes/titleBar-dark.scss'; 2 | @import '../Themes/titleBar-light.scss'; 3 | 4 | 5 | 6 | .title-bar { 7 | 8 | display: flex; 9 | flex-direction: row; 10 | align-items: center; 11 | 12 | 13 | &-main { 14 | -webkit-app-region: drag; 15 | -webkit-user-select: none; 16 | flex: 1; 17 | text-align: center; 18 | font-size: 1.0em; 19 | } 20 | 21 | &-icon { 22 | -webkit-app-region: drag; 23 | -webkit-user-select: none; 24 | padding: 0.4em 0.8em; 25 | 26 | } 27 | 28 | &-controls { 29 | 30 | ul { 31 | margin: 0; 32 | padding: 0; 33 | } 34 | 35 | li { 36 | 37 | list-style: none; 38 | padding: 0.4em 0.8em; 39 | display: inline-block; 40 | font-size: 1em; 41 | &:hover { 42 | cursor: pointer; 43 | } 44 | } 45 | 46 | .btn-window-close { 47 | &:hover { 48 | background-color: $color-red; 49 | color: #fff; 50 | } 51 | } 52 | } 53 | } 54 | 55 | .menu-item { 56 | &-container { 57 | display: flex; 58 | flex-direction: row; 59 | } 60 | 61 | &-label { 62 | flex: 1; 63 | } 64 | 65 | &-accelerator { 66 | text-align: right; 67 | margin-left: 2em; 68 | } 69 | 70 | &-checkbox { 71 | font-size: 0.8em; 72 | position: absolute; 73 | margin-left: -1.5em; 74 | } 75 | } 76 | 77 | .rc-menu { 78 | outline: none; 79 | margin-bottom: 0; 80 | padding-left: 0; // Override default ul/ol 81 | list-style: none; 82 | font-size: 12px; 83 | 84 | &-hidden, &-submenu-hidden { 85 | display: none; 86 | } 87 | 88 | &-collapse { 89 | overflow: hidden; 90 | &-active { 91 | transition: height .3s ease-out; 92 | } 93 | } 94 | 95 | &-item-group-list { 96 | margin: 0; 97 | padding: 0; 98 | } 99 | 100 | &-item-group-title { 101 | line-height: 1.5; 102 | padding: 8px 10px; 103 | } 104 | 105 | &-item-active, 106 | &-submenu-active > &-submenu-title { 107 | cursor: pointer; 108 | 109 | 110 | } 111 | 112 | &-item-selected { 113 | background-color: red; 114 | // fix chrome render bug 115 | transform: translateZ(0); 116 | } 117 | 118 | &-submenu-selected { 119 | background-color:red; 120 | } 121 | 122 | & > li { 123 | &-submenu { 124 | padding: 0; 125 | } 126 | } 127 | 128 | &-horizontal, &-sub, &-vertical, &-sub, &-vertical-left, &-sub, &-vertical-right, &-sub { 129 | min-width: 160px; 130 | margin-top: 0; 131 | } 132 | 133 | &-item, &-submenu-title { 134 | margin: 0; 135 | position: relative; 136 | display: block; 137 | padding: 0 1em; 138 | height: 34px; 139 | line-height: 34px; 140 | white-space: nowrap; 141 | 142 | // Disabled state sets text to gray and nukes hover/tab effects 143 | &.rc-menu-item-disabled, &.rc-menu-submenu-disabled { 144 | color: #777 !important; 145 | } 146 | } 147 | & > &-item-divider { 148 | height: 1px; 149 | margin: 1px 0; 150 | overflow: hidden; 151 | padding: 0; 152 | line-height: 0; 153 | } 154 | 155 | &-submenu { 156 | &-popup { 157 | position: absolute; 158 | background-color: $main-bg-color; 159 | border: 1px solid $toolbar-bg-color; 160 | box-shadow: 0px 2px 5px $toolbar-bg-color; 161 | 162 | .submenu-title-wrapper { 163 | padding-right: 20px; 164 | } 165 | } 166 | > .rc-menu { 167 | background-color: $main-bg-color; 168 | } 169 | } 170 | 171 | .rc-menu-submenu-title, .rc-menu-item { 172 | .anticon { 173 | width: 14px; 174 | height: 14px; 175 | margin-right: 8px; 176 | top: -1px; 177 | } 178 | } 179 | 180 | &-horizontal { 181 | border: none; 182 | box-shadow: none; 183 | white-space: nowrap; 184 | overflow: hidden; 185 | 186 | 187 | & > .rc-menu-submenu, & > .rc-menu-item { 188 | display: inline-block; 189 | vertical-align: bottom; 190 | 191 | } 192 | 193 | &:after { 194 | content: "\20"; 195 | display: block; 196 | height: 0; 197 | clear: both; 198 | } 199 | } 200 | 201 | &-vertical, 202 | &-vertical-left, 203 | &-vertical-right, 204 | &-inline { 205 | padding: 12px 0; 206 | & > .rc-menu-item, & > .rc-menu-submenu > .rc-menu-submenu-title { 207 | padding: 12px 8px 12px 24px; 208 | } 209 | .rc-menu-submenu-arrow { 210 | display: inline-block; 211 | font: normal normal normal 14px/1 FontAwesome; 212 | font-size: inherit; 213 | vertical-align: baseline; 214 | text-align: center; 215 | text-transform: none; 216 | text-rendering: auto; 217 | position: absolute; 218 | right: 16px; 219 | line-height: 34px; 220 | &:before { 221 | content: "\f0da"; 222 | } 223 | } 224 | } 225 | &-inline { 226 | .rc-menu-submenu-arrow { 227 | transform: rotate(90deg); 228 | transition: transform .3s; 229 | } 230 | & .rc-menu-submenu-open > .rc-menu-submenu-title { 231 | .rc-menu-submenu-arrow { 232 | transform: rotate(-90deg); 233 | } 234 | } 235 | } 236 | 237 | &-vertical, &-sub, 238 | &-vertical-left, &-sub, 239 | &-vertical-right, &-sub { 240 | padding: 0; 241 | } 242 | 243 | &-sub, &-inline { 244 | padding: 0; 245 | border: none; 246 | box-shadow: none; 247 | 248 | & > .rc-menu-item, & > .rc-menu-submenu > .rc-menu-submenu-title { 249 | padding: 0 2em; 250 | line-height: 34px; 251 | } 252 | } 253 | 254 | &-open { 255 | 256 | &-slide-up-enter, &-slide-up-appear { 257 | opacity: 0; 258 | animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); 259 | animation-play-state: paused; 260 | } 261 | 262 | &-slide-up-leave { 263 | opacity: 1; 264 | animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); 265 | animation-play-state: paused; 266 | } 267 | 268 | &-slide-up-enter, &-slide-up-enter-active, &-slide-up-appear, &-slide-up-appear-active { 269 | animation-name: rcMenuOpenSlideUpIn; 270 | animation-play-state: running; 271 | } 272 | 273 | &-slide-up-leave, &-slide-up-leave-active { 274 | animation-name: rcMenuOpenSlideUpOut; 275 | animation-play-state: running; 276 | } 277 | 278 | @keyframes rcMenuOpenSlideUpIn { 279 | 0% { 280 | opacity: 0; 281 | transform-origin: 0% 0%; 282 | transform: scaleY(0); 283 | } 284 | 100% { 285 | opacity: 1; 286 | transform-origin: 0% 0%; 287 | transform: scaleY(1); 288 | } 289 | } 290 | @keyframes rcMenuOpenSlideUpOut { 291 | 0% { 292 | opacity: 1; 293 | transform-origin: 0% 0%; 294 | transform: scaleY(1); 295 | } 296 | 100% { 297 | opacity: 0; 298 | transform-origin: 0% 0%; 299 | transform: scaleY(0); 300 | } 301 | } 302 | 303 | &-zoom-enter, &-zoom-appear { 304 | opacity: 0; 305 | animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); 306 | animation-play-state: paused; 307 | } 308 | 309 | &-zoom-leave { 310 | animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); 311 | animation-play-state: paused; 312 | } 313 | 314 | &-zoom-enter, &-zoom-enter-active, &-zoom-appear, &-zoom-appear-active { 315 | animation-name: rcMenuOpenZoomIn; 316 | animation-play-state: running; 317 | } 318 | 319 | &-zoom-leave, &-zoom-leave-active { 320 | animation-name: rcMenuOpenZoomOut; 321 | animation-play-state: running; 322 | } 323 | 324 | @keyframes rcMenuOpenZoomIn { 325 | 0% { 326 | opacity: 0; 327 | transform: scale(0, 0); 328 | } 329 | 100% { 330 | opacity: 1; 331 | transform: scale(1, 1); 332 | } 333 | } 334 | @keyframes rcMenuOpenZoomOut { 335 | 0% { 336 | 337 | transform: scale(1, 1); 338 | } 339 | 100% { 340 | opacity: 0; 341 | transform: scale(0, 0); 342 | } 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/Images/app-icons/bashwizard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Themes/dark/theme.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | :root { 3 | --main-bg-color: #353535; 4 | --main-fg-color: #a5a5a5; 5 | --main-border-color: #a5a5a5; 6 | --highlight-fg-color: #e5e5e5; 7 | --highlight-border-color: #e5e5e5; 8 | --toolbar-bg-color: #252525; 9 | --toolbar-border-color: #a5a5a5; 10 | --button-bg-color: #252525; 11 | --button-fg-color: #a5a5a5; 12 | --button-border-color: #252525; 13 | --button-hover-bg-color: #555555; 14 | --button-hover-fg-color: #e5e5e5; 15 | --button-hover-border-color: #e5e5e5; 16 | --button-selected-fg-color: #e5e5e5; 17 | --button-selected-bg-color: #454545; 18 | --button-selected-border-color: #e5e5e5; 19 | --list-item-background: #7c7c7c; 20 | } 21 | 22 | body.dark .DIV_Top { 23 | background-color: var(--main-bg-color); 24 | } 25 | body.dark .bw-button { 26 | background-color: var(--button-bg-color); 27 | color: var(--main-fg-color); 28 | border-color: var(--button-border-color); 29 | } 30 | 31 | body.dark .p-menuitem { 32 | background-color: var(--button-bg-color); 33 | } 34 | 35 | body.dark .p-menuitem-icon, body.dark .p-menuitem-text { 36 | background-color: transparent ; 37 | color: var(--button-fg-color) !important; 38 | } 39 | 40 | body.dark .p-menuitem:hover { 41 | background-color: var(--button-hover-bg-color) ; 42 | color: var(--button-hover-fg-color) ; 43 | } 44 | 45 | body.dark .bw-button { 46 | background-color: var(--button-bg-color); 47 | color: var(--main-fg-color); 48 | border-color: var(--button-border-color); 49 | } 50 | 51 | 52 | 53 | body.dark .svg-file-new path { 54 | stroke:var(--main-fg-color); 55 | fill: var(--main-fg-color); 56 | stroke-width: 0.75px; 57 | } 58 | 59 | body.dark .bw-button:hover { 60 | background-color: var(--button-hover-bg-color); 61 | color: var(--button-hover-fg-color); 62 | border-color: var(--button-hover-border-color); 63 | } 64 | 65 | body.dark .Resizer.horizontal { 66 | background-color: var(--button-bg-color); 67 | border-color: var(--button-border-color); 68 | ; 69 | } 70 | 71 | body.dark .collapse-button { 72 | border-color: var(--button-border-color) !important; 73 | } 74 | 75 | body.dark { 76 | background-color: var(--main-bg-color); 77 | } 78 | 79 | body.dark .p-toolbar { 80 | background-color: var(--toolbar-bg-color); 81 | border-color: var(--toolbar-border-color); 82 | } 83 | 84 | body.dark .p-listbox { 85 | background: var(--main-bg-color); 86 | border-color: var(--main-border-color); 87 | } 88 | 89 | body.dark .p-listbox .p-listbox-list { 90 | background-color: var(--main-bg-color); 91 | } 92 | 93 | body.dark .p-listbox .p-listbox-list .p-listbox-item { 94 | color: var(--main-fg-color); 95 | } 96 | 97 | body.dark .p-listbox .p-listbox-list .p-listbox-item.p-highlight { 98 | color: var(--button-selected-fg-color); 99 | background-color: var(--list-item-background); 100 | } 101 | 102 | body.dark .p-listbox .p-listbox-list .p-listbox-item:focus { 103 | -webkit-box-shadow: inset 0 0 0 0.2em var(--highlight-border-color); 104 | -moz-box-shadow: inset 0 0 0 0.2em var(--highlight-border-color); 105 | box-shadow: inset 0 0 0 0.2em var(--highlight-border-color); 106 | } 107 | 108 | body.dark .p-listbox:not(.p-disabled) .p-listbox-item:not(.p-highlight):not(.p-disabled):hover { 109 | color: var(--main-fg-color); 110 | background-color: var(--main-bg-color); 111 | } 112 | 113 | body.dark .p-listbox.p-disabled .p-checkbox-box:not(.p-disabled):not(.p-highlight):hover { 114 | border-color: var(--main-border-color); 115 | } 116 | 117 | body.dark .p-inputtext { 118 | color: var(--main-fg-color); 119 | background: var(--main-bg-color); 120 | border-color: var(--main-border-color); 121 | } 122 | 123 | body.dark .p-inputtext:enabled:hover { 124 | border-color: var(--button-hover-fg-color); 125 | } 126 | 127 | body.dark .p-inputtext:enabled:focus { 128 | -webkit-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 129 | -moz-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 130 | box-shadow: 0 0 0 0.2em var(--highlight-border-color); 131 | border-color: #000000; 132 | } 133 | 134 | body.dark .p-checkbox .p-checkbox-box { 135 | border-color: var(--main-border-color); 136 | background: var(--main-bg-color); 137 | } 138 | 139 | body.dark .p-checkbox .p-checkbox-box:not(.p-disabled):hover { 140 | background-color: var(--button-hover-bg-color); 141 | border-color: var(--button-hover-border-color); 142 | color: var(--button-hover-fg-color); 143 | } 144 | 145 | body.dark .p-checkbox .p-checkbox-box:not(.p-disabled).p-focus { 146 | -webkit-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 147 | -moz-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 148 | box-shadow: 0 0 0 0.2em var(--highlight-border-color); 149 | border-color: #000000; 150 | } 151 | 152 | body.dark .p-checkbox .p-checkbox-box.p-highlight { 153 | background-color: var(--button-selected-bg-color); 154 | border-color: var(--button-selected-border-color); 155 | color: var(--button-selected-fg-color); 156 | } 157 | 158 | body.dark .p-checkbox .p-checkbox-box.p-highlight:not(.p-disabled):hover { 159 | background-color: var(--button-hover-bg-color); 160 | border-color: var(--button-hover-border-color); 161 | color: var(--button-hover-fg-color); 162 | } 163 | 164 | body.dark .p-button.p-button-secondary, body.dark .p-buttonset.p-button-secondary>.p-button { 165 | color: var(--button-fg-color); 166 | background-color: var(--button-bg-color); 167 | border: 1px solid var(--button-border-color); 168 | } 169 | 170 | body.dark .p-button.p-button-secondary:enabled:hover, body.dark .p-buttonset.p-button-secondary>.p-button:enabled:hover { 171 | background-color: var(--button-hover-bg-color); 172 | color: var(--button-hover-fg-color); 173 | border-color: var(--button-hover-border-color); 174 | ; 175 | } 176 | 177 | body.dark .p-togglebutton { 178 | background-color: var(--button-hover-bg-color); 179 | color: var(--button-hover-fg-color); 180 | border-color: var(--button-hover-border-color); 181 | ; 182 | } 183 | 184 | body.dark .p-togglebutton:not(.p-disabled):hover { 185 | background-color: var(--button-hover-bg-color); 186 | border-color: var(--button-hover-border-color); 187 | color: var(--button-hover-fg-color); 188 | } 189 | 190 | body.dark .p-togglebutton.p-focus { 191 | -webkit-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 192 | -moz-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 193 | box-shadow: 0 0 0 0.2em var(--highlight-border-color); 194 | } 195 | 196 | body.dark .p-togglebutton.p-highlight { 197 | background-color: var(--button-bg-color); 198 | border-color: var(--button-border-color); 199 | color: var(--button-selected-fg-color); 200 | } 201 | 202 | body.dark .p-button.p-button-secondary:enabled:focus, body.dark .p-buttonset.p-button-secondary>.p-button:enabled:focus { 203 | -webkit-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 204 | -moz-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 205 | box-shadow: 0 0 0 0.2em var(--highlight-border-color); 206 | } 207 | 208 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li a, body.dark .p-tabview.p-tabview-bottom .p-tabview-nav li a, body.dark.p-tabview.p-tabview-left .p-tabview-nav li a, body.dark.p-tabview.p-tabview-right .p-tabview-nav li a { 209 | border: 1px solid var(--main-border-color); 210 | background-color: var(--button-bg-color); 211 | color: var(--button-fg-color); 212 | } 213 | 214 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li a:not(.p-disabled):focus, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li a:not(.p-disabled):focus, body.dark.p-tabview.p-tabview-left .p-tabview-nav li a:not(.p-disabled):focus, body.dark.p-tabview.p-tabview-right .p-tabview-nav li a:not(.p-disabled):focus { 215 | -webkit-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 216 | -moz-box-shadow: 0 0 0 0.2em var(--highlight-border-color); 217 | box-shadow: 0 0 0 0.2em var(--highlight-border-color); 218 | } 219 | 220 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li:not(.p-highlight):not(.p-disabled):hover a, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li:not(.p-highlight):not(.p-disabled):hover a, body.dark.p-tabview.p-tabview-left .p-tabview-nav li:not(.p-highlight):not(.p-disabled):hover a, body.dark.p-tabview.p-tabview-right .p-tabview-nav li:not(.p-highlight):not(.p-disabled):hover a { 221 | background-color: var(--button-hover-bg-color); 222 | color: var(--button-hover-fg-color); 223 | border-color: var(--button-hover-border-color); 224 | ; 225 | } 226 | 227 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li.p-highlight a, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li.p-highlight a, body.dark.p-tabview.p-tabview-left .p-tabview-nav li.p-highlight a, body.dark.p-tabview.p-tabview-right .p-tabview-nav li.p-highlight a { 228 | background-color: var(--button-selected-bg-color); 229 | border-color: var(--main-border-color); 230 | color: var(--button-selected-fg-color); 231 | } 232 | 233 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li.p-highlight .p-tabview-close, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li.p-highlight .p-tabview-close, body.dark.p-tabview.p-tabview-left .p-tabview-nav li.p-highlight .p-tabview-close, body.dark.p-tabview.p-tabview-right .p-tabview-nav li.p-highlight .p-tabview-close { 234 | color: var(--button-selected-fg-color); 235 | } 236 | 237 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li.p-highlight:hover a, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li.p-highlight:hover a, body.dark.p-tabview.p-tabview-left .p-tabview-nav li.p-highlight:hover a, body.dark.p-tabview.p-tabview-right .p-tabview-nav li.p-highlight:hover a { 238 | background-color: var(--button-selected-bg-color); 239 | border-color: var(--button-selected-border-color); 240 | color: var(--button-selected-fg-color); 241 | } 242 | 243 | body.dark .p-tabview.p-tabview-top .p-tabview-nav li.p-highlight:hover a .p-tabview-left-icon, body.dark.p-tabview.p-tabview-top .p-tabview-nav li.p-highlight:hover a .p-tabview-right-icon, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li.p-highlight:hover a .p-tabview-left-icon, body.dark.p-tabview.p-tabview-bottom .p-tabview-nav li.p-highlight:hover a .p-tabview-right-icon, body.dark.p-tabview.p-tabview-left .p-tabview-nav li.p-highlight:hover a .p-tabview-left-icon, body.dark.p-tabview.p-tabview-left .p-tabview-nav li.p-highlight:hover a .p-tabview-right-icon, body.dark.p-tabview.p-tabview-right .p-tabview-nav li.p-highlight:hover a .p-tabview-left-icon, body.dark.p-tabview.p-tabview-right .p-tabview-nav li.p-highlight:hover a .p-tabview-right-icon { 244 | color: var(--button-selected-fg-color); 245 | } 246 | 247 | body.dark .p-tabview .p-tabview-panels { 248 | background-color: var(--main-bg-color); 249 | border-color: var(--main-border-color); 250 | color: var(--main-fg-color); 251 | } 252 | -------------------------------------------------------------------------------- /src/Components/titleBar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Menu, { MenuItem, SubMenu, Divider } from "rc-menu"; 3 | import "./titleBar.scss"; 4 | 5 | export interface ITitleBarProps extends React.Props { 6 | icon?: string | JSX.Element; 7 | title?: string; 8 | } 9 | 10 | export interface ITitleBarState { 11 | isElectron: boolean; 12 | maximized: boolean; 13 | minimized: boolean; 14 | menu: any; 15 | fullScreen: boolean; 16 | } 17 | 18 | export class TitleBar extends React.Component { 19 | public state: ITitleBarState = { 20 | isElectron: false, 21 | maximized: false, 22 | minimized: false, 23 | menu: undefined, 24 | fullScreen: false, 25 | }; 26 | 27 | private menu: Menu = React.createRef(); 28 | private remote: Electron.Remote; 29 | private currentWindow: Electron.BrowserWindow; 30 | 31 | public componentDidMount() { 32 | const isElectron: boolean = !!window["require"]; 33 | 34 | if (isElectron) { 35 | this.remote = (window as any).require("electron").remote as Electron.Remote; 36 | this.currentWindow = this.remote.getCurrentWindow(); 37 | 38 | this.setState({ 39 | isElectron: true, 40 | minimized: this.currentWindow.isMinimized(), 41 | maximized: this.currentWindow.isMaximized(), 42 | menu: this.remote.Menu.getApplicationMenu(), 43 | }); 44 | const ipcRenderer: Electron.IpcRenderer = (window as any).require("electron").ipcRenderer as Electron.IpcRenderer; 45 | if (ipcRenderer !== null) { 46 | ipcRenderer.on("window-position-changed", this.onWindowsPosChanged); 47 | } 48 | 49 | } 50 | 51 | } 52 | 53 | private onWindowsPosChanged = (event: any, message: string): void => { 54 | // console.log(`onWindowsPosChanged: ${message}`) 55 | if (this.state.fullScreen && message === "maximized") { 56 | // when you are maximized and go into full screen, you get another maximized event. 57 | return; 58 | } 59 | this.setState({ maximized: message === "maximized", minimized: message === "minimized", fullScreen: message === "enter-full-screen" }) 60 | } 61 | 62 | public componentDidUpdate(prevProps: Readonly) { 63 | if (this.props.title !== prevProps.title) { 64 | this.syncTitle(); 65 | } 66 | } 67 | 68 | private get onMacOrBrowser(): boolean { 69 | if (this.remote) { 70 | if (this.remote.process.platform === "darwin") { 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | public render(): JSX.Element | null { 80 | if (this.onMacOrBrowser) { 81 | return null; 82 | } 83 | 84 | // 85 | // if the screen is maximized or we are running on a mac, make the titlebar have 0px height 86 | let tbHeight: string = "34px"; 87 | if (this.state.fullScreen) { 88 | tbHeight = "0px"; 89 | } 90 | // console.log("Titlebar title: " + this.state.title) 91 | return ( 92 |
93 |
94 | {typeof (this.props.icon) === "string" && } 95 | {typeof (this.props.icon) !== "string" && this.props.icon} 96 |
97 |
98 | {this.state.isElectron && 99 | 104 | {this.renderMenu(this.state.menu)} 105 | 106 | } 107 |
108 |
{this.props.title || "UNSET TITLE - YOU SHOULD FIX THIS"}
109 |
110 | {this.props.children} 111 | {this.state.isElectron && 112 |
    113 |
  • 114 | 115 |
  • 116 | {!this.state.maximized && 117 |
  • 118 | 119 |
  • 120 | } 121 | {this.state.maximized && 122 |
  • 123 | 124 |
  • 125 | } 126 |
  • 127 | 128 |
  • 129 |
130 | } 131 |
132 |
133 | ); 134 | } 135 | 136 | 137 | private renderMenu = (menu: Electron.Menu) => { 138 | if (!menu) { 139 | return null; 140 | } 141 | 142 | return menu.items.map(this.renderMenuItem); 143 | } 144 | 145 | private renderMenuItem = (menuItem: Electron.MenuItem, index: number) => { 146 | if (!menuItem.visible) { 147 | return null; 148 | } 149 | 150 | const itemType: string = menuItem["type"]; 151 | 152 | switch (itemType) { 153 | case "submenu": 154 | return ( 155 | 156 | {this.renderMenu(menuItem["submenu"])} 157 | 158 | ); 159 | case "separator": 160 | return (); 161 | case "checkbox": 162 | return ( 163 | this.onMenuItemClick(e, menuItem)}> 166 |
167 | {Boolean(menuItem.checked) && 168 |
169 | 170 |
171 | } 172 |
{menuItem.label}{menuItem["sublabel"]}
173 |
{this.getAcceleratorString(menuItem)}
174 |
175 |
); 176 | case "normal": 177 | return ( 178 | this.onMenuItemClick(e, menuItem)}> 181 |
182 |
{menuItem.label}{menuItem["sublabel"]}
183 |
{this.getAcceleratorString(menuItem)}
184 |
185 |
186 | ); 187 | } 188 | 189 | return null; 190 | } 191 | 192 | private onMenuItemClick(e: any, menuItem: Electron.MenuItem) { 193 | if (menuItem.click) { 194 | menuItem.click.call(menuItem, menuItem, this.currentWindow); 195 | } 196 | 197 | this.setState({ menu: { ...this.state.menu } as Electron.Menu }); 198 | } 199 | 200 | public syncTitle = (): void => { 201 | if (this.state.isElectron) { 202 | this.currentWindow.setTitle(`${this.props.title}`); 203 | } 204 | } 205 | 206 | private minimizeWindow = () => { 207 | this.currentWindow.minimize(); 208 | this.setState({ minimized: true }); 209 | } 210 | 211 | private maximizeWindow = () => { 212 | this.currentWindow.maximize(); 213 | this.setState({ maximized: true, minimized: false }); 214 | } 215 | 216 | private restoreWindow = () => { 217 | this.currentWindow.restore(); 218 | this.setState({ maximized: false, minimized: false }); 219 | } 220 | 221 | private closeWindow = () => { 222 | this.remote.getCurrentWindow().close(); 223 | } 224 | 225 | private onMenuItemSelected = (key: string, item: React.Component) => { 226 | this.menu.current.store.setState({ 227 | openKeys: [], 228 | selectedKeys: [], 229 | }); 230 | } 231 | 232 | private getAcceleratorString(menuItem: Electron.MenuItem) { 233 | const accelerator = menuItem["accelerator"] || this.getAcceleratorFromRole(menuItem["role"]); 234 | if (accelerator) { 235 | return accelerator.replace("CmdOrCtrl", "Ctrl"); 236 | } 237 | 238 | return null; 239 | } 240 | 241 | private getAcceleratorFromRole(role: string) { 242 | switch (role) { 243 | case "undo": 244 | return "CmdOrCtrl+Z"; 245 | case "redo": 246 | return "CmdOrCtrl+Y"; 247 | case "cut": 248 | return "CmdOrCtrl+X"; 249 | case "copy": 250 | return "CmdOrCtrl+C"; 251 | case "paste": 252 | return "CmdOrCtrl+V"; 253 | case "selectall": 254 | return "CmdOrCtrl+A"; 255 | case "minimize": 256 | return "CmdOrCtrl+M"; 257 | case "close": 258 | return "CmdOrCtrl+W"; 259 | case "quit": 260 | return "CmdOrCtrl+Q"; 261 | case "reload": 262 | return "CmdOrCtrl+R"; 263 | case "togglefullscreen": 264 | return "F11"; 265 | case "toggledevtools": 266 | return "CmdOrCtrl+Shift+I"; 267 | case "resetzoom": 268 | return "CmdOrCtrl+0"; 269 | case "zoomin": 270 | return "CmdOrCtrl+Shift+="; 271 | case "zoomout": 272 | return "CmdOrCtrl+-"; 273 | } 274 | return null; 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /test/createKeyVault2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #---------- see https://github.com/joelong01/Bash-Wizard---------------- 3 | # bashWizard version 0.90911 4 | # this will make the error text stand out in red - if you are looking at these errors/warnings in the log file 5 | # you can use cat to see the text in color. 6 | function echoError() { 7 | RED=$(tput setaf 1) 8 | NORMAL=$(tput sgr0) 9 | echo "${RED}${1}${NORMAL}" 10 | } 11 | function echoWarning() { 12 | YELLOW=$(tput setaf 3) 13 | NORMAL=$(tput sgr0) 14 | echo "${YELLOW}${1}${NORMAL}" 15 | } 16 | function echoInfo { 17 | GREEN=$(tput setaf 2) 18 | NORMAL=$(tput sgr0) 19 | echo "${GREEN}${1}${NORMAL}" 20 | } 21 | # make sure this version of *nix supports the right getopt 22 | ! getopt --test 2>/dev/null 23 | if [[ ${PIPESTATUS[0]} -ne 4 ]]; then 24 | echoError "'getopt --test' failed in this environment. please install getopt." 25 | read -r -p "install getopt using brew? [y,n]" response 26 | if [[ $response == 'y' ]] || [[ $response == 'Y' ]]; then 27 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null 28 | brew install gnu-getopt 29 | #shellcheck disable=SC2016 30 | echo 'export PATH="/usr/local/opt/gnu-getopt/bin:$PATH"' >> ~/.bash_profile 31 | echoWarning "you'll need to restart the shell instance to load the new path" 32 | fi 33 | exit 1 34 | fi 35 | # we have a dependency on jq 36 | if [[ ! -x "$(command -v jq)" ]]; then 37 | echoError "'jq is needed to run this script. Please install jq - see https://stedolan.github.io/jq/download/" 38 | exit 1 39 | fi 40 | function usage() { 41 | echoWarning "Parameters can be passed in the command line or in the input file. The command line overrides the setting in the input file." 42 | echo "creates an Azure Key Vault" 43 | echo "" 44 | echo "Usage: $0 -u|--sku -e|--enabled-for-disk-encryption -p|--enable-for-deployment -k|--keyvault-name -l|--datacenter-location -r|--resource-group -v|--verify-script -c|--create -d|--delete -i|--input-file -o|--log-directory " 1>&2 45 | echo "" 46 | echo " -u | --sku Optional SKU details. ccepted values: premium, standard" 47 | echo " -e | --enabled-for-disk-encryption Optional Allow Disk Encryption to retrieve secrets from the vault and unwrap keys." 48 | echo " -p | --enable-for-deployment Optional Allow Virtual Machines to retrieve certificates stored as secrets from the vault." 49 | echo " -k | --keyvault-name Optional the name of the keyvault" 50 | echo " -l | --datacenter-location Required the location of the VMs" 51 | echo " -r | --resource-group Required Azure Resource Group" 52 | echo " -v | --verify-script Optional " 53 | echo " -c | --create Optional create the key vault. idempotent." 54 | echo " -d | --delete Optional delete key Vault if it already exists" 55 | echo " -i | --input-file Optional filename that contains the JSON values to drive the script. command line overrides file" 56 | echo " -o | --log-directory Optional directory for the log file. the log file name will be based on the script name" 57 | echo "" 58 | exit 1 59 | } 60 | function echoInput() { 61 | echo "createKeyVault.sh:" 62 | echo -n " sku............................ " 63 | echoInfo "$sku" 64 | echo -n " enabled-for-disk-encryption.... " 65 | echoInfo "$enableForDiskEncryption" 66 | echo -n " enable-for-deployment.......... " 67 | echoInfo "$enableForDeployment" 68 | echo -n " keyvault-name.................. " 69 | echoInfo "$keyvaultName" 70 | echo -n " datacenter-location............ " 71 | echoInfo "$datacenterLocation" 72 | echo -n " resource-group................. " 73 | echoInfo "$resourceGroup" 74 | echo -n " verify-script.................. " 75 | echoInfo "$verifyScript" 76 | echo -n " create......................... " 77 | echoInfo "$createKeyVault" 78 | echo -n " delete......................... " 79 | echoInfo "$deleteKeyVault" 80 | echo -n " input-file..................... " 81 | echoInfo "$inputFile" 82 | echo -n " log-directory.................. " 83 | echoInfo "$logDirectory" 84 | 85 | } 86 | 87 | function parseInput() { 88 | 89 | local OPTIONS=u:e:p:k:l:r:vcdi:o: 90 | local LONGOPTS=sku:,enabled-for-disk-encryption:,enable-for-deployment:,keyvault-name:,datacenter-location:,resource-group:,verify-script,create,delete,input-file:,log-directory: 91 | 92 | # -use ! and PIPESTATUS to get exit code with errexit set 93 | # -temporarily store output to be able to check for errors 94 | # -activate quoting/enhanced mode (e.g. by writing out "--options") 95 | # -pass arguments only via -- "$@" to separate them correctly 96 | ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") 97 | if [[ ${PIPESTATUS[0]} -ne 0 ]]; then 98 | # e.g. return value is 1 99 | # then getopt has complained about wrong arguments to stdout 100 | usage 101 | exit 2 102 | fi 103 | # read getopt's output this way to handle the quoting right: 104 | eval set -- "$PARSED" 105 | while true; do 106 | case "$1" in 107 | -u | --sku) 108 | sku=$2 109 | shift 2 110 | ;; 111 | -e | --enabled-for-disk-encryption) 112 | enableForDiskEncryption=$2 113 | shift 2 114 | ;; 115 | -p | --enable-for-deployment) 116 | enableForDeployment=$2 117 | shift 2 118 | ;; 119 | -k | --keyvault-name) 120 | keyvaultName=$2 121 | shift 2 122 | ;; 123 | -l | --datacenter-location) 124 | datacenterLocation=$2 125 | shift 2 126 | ;; 127 | -r | --resource-group) 128 | resourceGroup=$2 129 | shift 2 130 | ;; 131 | -v | --verify-script) 132 | verifyScript=true 133 | shift 1 134 | ;; 135 | -c | --create) 136 | createKeyVault=true 137 | shift 1 138 | ;; 139 | -d | --delete) 140 | deleteKeyVault=true 141 | shift 1 142 | ;; 143 | -i | --input-file) 144 | inputFile=$2 145 | shift 2 146 | ;; 147 | -o | --log-directory) 148 | logDirectory=$2 149 | shift 2 150 | ;; 151 | --) 152 | shift 153 | break 154 | ;; 155 | *) 156 | echoError "Invalid option $1 $2" 157 | exit 3 158 | ;; 159 | esac 160 | done 161 | } 162 | # input variables 163 | declare sku="standard" 164 | declare enableForDiskEncryption=true 165 | declare enableForDeployment=true 166 | declare keyvaultName="" 167 | declare datacenterLocation= 168 | declare resourceGroup= 169 | declare verifyScript=false 170 | declare createKeyVault=false 171 | declare deleteKeyVault=false 172 | declare inputFile="../Data/cseAzureAutomationConfig.json" 173 | declare logDirectory=./logs/ 174 | 175 | parseInput "$@" 176 | 177 | # if command line tells us to parse an input file 178 | if [ "${inputFile}" != "" ]; then 179 | # load parameters from the file 180 | configSection=$(jq . <"${inputFile}" | jq '."createKeyVault.sh"') 181 | if [[ -z $configSection ]]; then 182 | echoError "$inputFile or createKeyVault.sh section not found " 183 | exit 3 184 | fi 185 | sku=$(echo "${configSection}" | jq '.["sku"]' --raw-output) 186 | enableForDiskEncryption=$(echo "${configSection}" | jq '.["enabled-for-disk-encryption"]' --raw-output) 187 | enableForDeployment=$(echo "${configSection}" | jq '.["enable-for-deployment"]' --raw-output) 188 | keyvaultName=$(echo "${configSection}" | jq '.["keyvault-name"]' --raw-output) 189 | datacenterLocation=$(echo "${configSection}" | jq '.["datacenter-location"]' --raw-output) 190 | resourceGroup=$(echo "${configSection}" | jq '.["resource-group"]' --raw-output) 191 | verifyScript=$(echo "${configSection}" | jq '.["verify-script"]' --raw-output) 192 | createKeyVault=$(echo "${configSection}" | jq '.["create"]' --raw-output) 193 | deleteKeyVault=$(echo "${configSection}" | jq '.["delete"]' --raw-output) 194 | logDirectory=$(echo "${configSection}" | jq '.["log-directory"]' --raw-output) 195 | 196 | # we need to parse the again to see if there are any overrides to what is in the config file 197 | parseInput "$@" 198 | fi 199 | #verify required parameters are set 200 | if [ -z "${datacenterLocation}" ] || [ -z "${resourceGroup}" ]; then 201 | echo "" 202 | echoError "Required parameter missing! " 203 | echoInput #make it easy to see what is missing 204 | echo "" 205 | usage 206 | exit 2 207 | fi 208 | #logging support 209 | declare LOG_FILE="${logDirectory}createKeyVault.sh.log" 210 | { 211 | mkdir -p "${logDirectory}" 212 | rm -f "${LOG_FILE}" 213 | } 2>>/dev/null 214 | #creating a tee so that we capture all the output to the log file 215 | { 216 | time=$(date +"%m/%d/%y @ %r") 217 | echo "started: $time" 218 | 219 | # --- BEGIN USER CODE --- 220 | function verifyKeyVault() { 221 | kvInfo=$(az keyvault list -g "$resourceGroup" --output json --query "[].{Name:name, ID:id}[?Name=='${keyvaultName}']") 222 | id=$(echo "$kvInfo" | jq '.[].ID' --raw-output) 223 | if [[ "$id" == "" ]]; then 224 | echo "false" 225 | else 226 | echo "true" 227 | fi 228 | } 229 | if [ "$verifyScript" == "true" ]; then 230 | echo "verifying that a keyvault named ${keyvaultName} is in $resourceGroup " 231 | exists=$(verifyKeyVault) 232 | if [ "$exists" == true ]; then 233 | echo "PASS" 234 | else 235 | echo "FAIL" 236 | fi 237 | #comment 238 | exit 239 | fi 240 | if [ "$deleteKeyVault" == true ]; then #NOTE: this will delete *and* purge! 241 | exists=$(verifyKeyVault) 242 | if [ "$exists" == true ]; then # don't delete if doesn't exist 243 | echo "deleting $keyvaultName..." 244 | az keyvault delete --name "$keyvaultName" --resource-group "$resourceGroup" 245 | az keyvault purge --name "$keyvaultName" --location "$datacenterLocation" 246 | else 247 | echo "$keyvaultName does not exist -- not deleting it." 248 | fi 249 | fi 250 | if [ "$createKeyVault" == true ]; then 251 | echo "registering the Key Vault Resource Provider" 252 | az provider register -n Microsoft.KeyVault 253 | echo "creating $keyvaultName..." 254 | kvInfo=$(az keyvault create --name "$keyvaultName" --resource-group "$resourceGroup" --location "$datacenterLocation" --enable-soft-delete true --enabled-for-deployment "$enableForDeployment" --enabled-for-disk-encryption "$enableForDiskEncryption" --sku "$sku") 255 | echo "Key Vault ID: $(echo "$kvInfo" | jq .id)" 256 | echo "Key Vault name: $(echo "$kvInfo" | jq .name)" 257 | echo "" 258 | fi 259 | # 260 | # --- END USER CODE --- 261 | 262 | time=$(date +"%m/%d/%y @ %r") 263 | echo "ended: $time" 264 | } | tee -a "${LOG_FILE}" 265 | -------------------------------------------------------------------------------- /test/createKeyVault3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #---------- see https://github.com/joelong01/Bash-Wizard---------------- 3 | # bashWizard version 0.909 4 | # this will make the error text stand out in red - if you are looking at these errors/warnings in the log file 5 | # you can use cat to see the text in color. 6 | function echoError() { 7 | RED=$(tput setaf 1) 8 | NORMAL=$(tput sgr0) 9 | echo "${RED}${1}${NORMAL}" 10 | } 11 | function echoWarning() { 12 | YELLOW=$(tput setaf 3) 13 | NORMAL=$(tput sgr0) 14 | echo "${YELLOW}${1}${NORMAL}" 15 | } 16 | function echoInfo { 17 | GREEN=$(tput setaf 2) 18 | NORMAL=$(tput sgr0) 19 | echo "${GREEN}${1}${NORMAL}" 20 | } 21 | # make sure this version of *nix supports the right getopt 22 | ! getopt --test 2>/dev/null 23 | if [[ ${PIPESTATUS[0]} -ne 4 ]]; then 24 | echoError "'getopt --test' failed in this environment. please install getopt." 25 | read -r -p "install getopt using brew? [y,n]" response 26 | if [[ $response == 'y' ]] || [[ $response == 'Y' ]]; then 27 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null 28 | brew install gnu-getopt 29 | #shellcheck disable=SC2016 30 | echo 'export PATH="/usr/local/opt/gnu-getopt/bin:$PATH"' >> ~/.bash_profile 31 | echoWarning "you'll need to restart the shell instance to load the new path" 32 | fi 33 | exit 1 34 | fi 35 | # we have a dependency on jq 36 | if [[ ! -x "$(command -v jq)" ]]; then 37 | echoError "'jq is needed to run this script. Please install jq - see https://stedolan.github.io/jq/download/" 38 | exit 1 39 | fi 40 | function usage() { 41 | echoWarning "Parameters can be passed in the command line or in the input file. The command line overrides the setting in the input file." 42 | echo "creates an Azure Key Vault" 43 | echo "" 44 | echo "Usage: $0 -u|--sku -e|--enabled-for-disk-encryption -p|--enable-for-deployment -k|--keyvault-name -l|--datacenter-location -r|--resource-group -v|--verify-script -c|--create -d|--delete -i|--input-file -o|--log-directory " 1>&2 45 | echo "" 46 | echo " -u | --sku Optional SKU details. ccepted values: premium, standard" 47 | echo " -e | --enabled-for-disk-encryption Optional Allow Disk Encryption to retrieve secrets from the vault and unwrap keys." 48 | echo " -p | --enable-for-deployment Optional Allow Virtual Machines to retrieve certificates stored as secrets from the vault." 49 | echo " -k | --keyvault-name Optional the name of the keyvault" 50 | echo " -l | --datacenter-location Required the location of the VMs" 51 | echo " -r | --resource-group Required Azure Resource Group" 52 | echo " -v | --verify-script Optional " 53 | echo " -c | --create Optional create the key vault. idempotent." 54 | echo " -d | --delete Optional delete key Vault if it already exists" 55 | echo " -i | --input-file Optional filename that contains the JSON values to drive the script. command line overrides file" 56 | echo " -o | --log-directory Optional directory for the log file. the log file name will be based on the script name" 57 | echo "" 58 | exit 1 59 | } 60 | function echoInput() { 61 | echo "createKeyVault.sh:" 62 | echo -n " sku............................ " 63 | echoInfo "$sku" 64 | echo -n " enabled-for-disk-encryption.... " 65 | echoInfo "$enableForDiskEncryption" 66 | echo -n " enable-for-deployment.......... " 67 | echoInfo "$enableForDeployment" 68 | echo -n " keyvault-name.................. " 69 | echoInfo "$keyvaultName" 70 | echo -n " datacenter-location............ " 71 | echoInfo "$datacenterLocation" 72 | echo -n " resource-group................. " 73 | echoInfo "$resourceGroup" 74 | echo -n " verify-script.................. " 75 | echoInfo "$verifyScript" 76 | echo -n " create......................... " 77 | echoInfo "$createKeyVault" 78 | echo -n " delete......................... " 79 | echoInfo "$deleteKeyVault" 80 | echo -n " input-file..................... " 81 | echoInfo "$inputFile" 82 | echo -n " log-directory.................. " 83 | echoInfo "$logDirectory" 84 | 85 | } 86 | 87 | function parseInput() { 88 | 89 | local OPTIONS=u:e:p:k:l:r:vcdi:o: 90 | local LONGOPTS=sku:,enabled-for-disk-encryption:,enable-for-deployment:,keyvault-name:,datacenter-location:,resource-group:,verify-script,create,delete,input-file:,log-directory: 91 | 92 | # -use ! and PIPESTATUS to get exit code with errexit set 93 | # -temporarily store output to be able to check for errors 94 | # -activate quoting/enhanced mode (e.g. by writing out "--options") 95 | # -pass arguments only via -- "$@" to separate them correctly 96 | ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") 97 | if [[ ${PIPESTATUS[0]} -ne 0 ]]; then 98 | # e.g. return value is 1 99 | # then getopt has complained about wrong arguments to stdout 100 | usage 101 | exit 2 102 | fi 103 | # read getopt's output this way to handle the quoting right: 104 | eval set -- "$PARSED" 105 | while true; do 106 | case "$1" in 107 | -u | --sku) 108 | sku=$2 109 | shift 2 110 | ;; 111 | -e | --enabled-for-disk-encryption) 112 | enableForDiskEncryption=$2 113 | shift 2 114 | ;; 115 | -p | --enable-for-deployment) 116 | enableForDeployment=$2 117 | shift 2 118 | ;; 119 | -k | --keyvault-name) 120 | keyvaultName=$2 121 | shift 2 122 | ;; 123 | -l | --datacenter-location) 124 | datacenterLocation=$2 125 | shift 2 126 | ;; 127 | -r | --resource-group) 128 | resourceGroup=$2 129 | shift 2 130 | ;; 131 | -v | --verify-script) 132 | verifyScript=true 133 | shift 1 134 | ;; 135 | -c | --create) 136 | createKeyVault=true 137 | shift 1 138 | ;; 139 | -d | --delete) 140 | deleteKeyVault=true 141 | shift 1 142 | ;; 143 | -i | --input-file) 144 | inputFile=$2 145 | shift 2 146 | ;; 147 | -o | --log-directory) 148 | logDirectory=$2 149 | shift 2 150 | ;; 151 | --) 152 | shift 153 | break 154 | ;; 155 | *) 156 | echoError "Invalid option $1 $2" 157 | exit 3 158 | ;; 159 | esac 160 | done 161 | } 162 | # input variables 163 | declare sku="standard" 164 | declare enableForDiskEncryption=true 165 | declare enableForDeployment=true 166 | declare keyvaultName="" 167 | declare datacenterLocation= 168 | declare resourceGroup= 169 | declare verifyScript=false 170 | declare createKeyVault=false 171 | declare deleteKeyVault=false 172 | declare inputFile="../Data/cseAzureAutomationConfig.json" 173 | declare logDirectory=./logs/ 174 | 175 | parseInput "$@" 176 | 177 | # if command line tells us to parse an input file 178 | if [ "${inputFile}" != "" ]; then 179 | # load parameters from the file 180 | configSection=$(jq . <"${inputFile}" | jq '."createKeyVault.sh"') 181 | if [[ -z $configSection ]]; then 182 | echoError "$inputFile or createKeyVault.sh section not found " 183 | exit 3 184 | fi 185 | sku=$(echo "${configSection}" | jq '.["sku"]' --raw-output) 186 | enableForDiskEncryption=$(echo "${configSection}" | jq '.["enabled-for-disk-encryption"]' --raw-output) 187 | enableForDeployment=$(echo "${configSection}" | jq '.["enable-for-deployment"]' --raw-output) 188 | keyvaultName=$(echo "${configSection}" | jq '.["keyvault-name"]' --raw-output) 189 | datacenterLocation=$(echo "${configSection}" | jq '.["datacenter-location"]' --raw-output) 190 | resourceGroup=$(echo "${configSection}" | jq '.["resource-group"]' --raw-output) 191 | verifyScript=$(echo "${configSection}" | jq '.["verify-script"]' --raw-output) 192 | createKeyVault=$(echo "${configSection}" | jq '.["create"]' --raw-output) 193 | deleteKeyVault=$(echo "${configSection}" | jq '.["delete"]' --raw-output) 194 | logDirectory=$(echo "${configSection}" | jq '.["log-directory"]' --raw-output) 195 | 196 | # we need to parse the again to see if there are any overrides to what is in the config file 197 | parseInput "$@" 198 | fi 199 | #verify required parameters are set 200 | if [ -z "${datacenterLocation}" ] || [ -z "${resourceGroup}" ]; then 201 | echo "" 202 | echoError "Required parameter missing! " 203 | echoInput #make it easy to see what is missing 204 | echo "" 205 | usage 206 | exit 2 207 | fi 208 | #logging support 209 | declare LOG_FILE="${logDirectory}createKeyVault.sh.log" 210 | { 211 | mkdir -p "${logDirectory}" 212 | rm -f "${LOG_FILE}" 213 | } 2>>/dev/null 214 | #creating a tee so that we capture all the output to the log file 215 | { 216 | time=$(date +"%m/%d/%y @ %r") 217 | echo "started: $time" 218 | 219 | # --- BEGIN USER CODE --- 220 | #test 221 | function verifyKeyVault() { 222 | kvInfo=$(az keyvault list -g "$resourceGroup" --output json --query "[].{Name:name, ID:id}[?Name=='${keyvaultName}']") 223 | id=$(echo "$kvInfo" | jq '.[].ID' --raw-output) 224 | if [[ "$id" == "" ]]; then 225 | echo "false" 226 | else 227 | echo "true" 228 | fi 229 | } 230 | if [ "$verifyScript" == "true" ]; then 231 | echo "verifying that a keyvault named ${keyvaultName} is in $resourceGroup " 232 | exists=$(verifyKeyVault) 233 | if [ "$exists" == true ]; then 234 | echo "PASS" 235 | else 236 | echo "FAIL" 237 | fi 238 | #comment 239 | exit 240 | fi 241 | if [ "$deleteKeyVault" == true ]; then #NOTE: this will delete *and* purge! 242 | exists=$(verifyKeyVault) 243 | if [ "$exists" == true ]; then # don't delete if doesn't exist 244 | echo "deleting $keyvaultName..." 245 | az keyvault delete --name "$keyvaultName" --resource-group "$resourceGroup" 246 | az keyvault purge --name "$keyvaultName" --location "$datacenterLocation" 247 | else 248 | echo "$keyvaultName does not exist -- not deleting it." 249 | fi 250 | fi 251 | if [ "$createKeyVault" == true ]; then 252 | echo "registering the Key Vault Resource Provider" 253 | az provider register -n Microsoft.KeyVault 254 | echo "creating $keyvaultName..." 255 | kvInfo=$(az keyvault create --name "$keyvaultName" --resource-group "$resourceGroup" --location "$datacenterLocation" --enable-soft-delete true --enabled-for-deployment "$enableForDeployment" --enabled-for-disk-encryption "$enableForDiskEncryption" --sku "$sku") 256 | echo "Key Vault ID: $(echo "$kvInfo" | jq .id)" 257 | echo "Key Vault name: $(echo "$kvInfo" | jq .name)" 258 | echo "" 259 | fi 260 | #test 261 | # --- END USER CODE --- 262 | 263 | time=$(date +"%m/%d/%y @ %r") 264 | echo "ended: $time" 265 | } | tee -a "${LOG_FILE}" 266 | -------------------------------------------------------------------------------- /src/Components/ParameterView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ParameterModel } from 'bash-models/ParameterModel'; 3 | import { ParameterType, IParameterState, } from "bash-models/commonModel" 4 | import { IGrowlCallback } from "../Models/bwCommonModels" 5 | import { InputText } from "primereact/inputtext" 6 | import { Checkbox } from "primereact/checkbox" 7 | import { Button } from "primereact/button" 8 | import { uniqueId } from 'lodash-es'; 9 | 10 | 11 | export interface IParameterProperties { 12 | Model: ParameterModel; 13 | key: string; 14 | label: string; 15 | GrowlCallback: IGrowlCallback; 16 | } 17 | 18 | interface IParameterViewState extends IParameterState { 19 | 20 | Model: ParameterModel; 21 | GrowlCallback: IGrowlCallback; 22 | type: ParameterType; 23 | collapsed: boolean; 24 | key: string; 25 | label: string; 26 | } 27 | 28 | export class ParameterView extends React.PureComponent { 29 | private refParameterForm = React.createRef(); 30 | private refLongName = React.createRef(); 31 | constructor(props: IParameterProperties) { 32 | super(props); 33 | const id: string = uniqueId("ParameterView"); 34 | this.state = { 35 | key: id, 36 | label: id, 37 | default: this.props.Model.default, 38 | description: this.props.Model.description, 39 | longParameter: this.props.Model.longParameter, 40 | requiresInputString: this.props.Model.requiresInputString, 41 | requiredParameter: this.props.Model.requiredParameter, 42 | shortParameter: this.props.Model.shortParameter, 43 | variableName: this.props.Model.variableName, 44 | valueIfSet: this.props.Model.valueIfSet, 45 | Model: this.props.Model, 46 | GrowlCallback: this.props.GrowlCallback, 47 | type: this.props.Model.type, 48 | collapsed: this.props.Model.collapsed 49 | }; 50 | 51 | 52 | 53 | } 54 | 55 | public componentWillMount() { 56 | this.state.Model.onPropertyChanged.subscribe(this.onPropertyChanged); 57 | 58 | } 59 | 60 | public componentWillUnmount() { 61 | this.props.Model.onPropertyChanged.unsubscribe(this.onPropertyChanged); 62 | } 63 | 64 | get Model(): ParameterModel { 65 | return this.state.Model; 66 | } 67 | 68 | 69 | private setStateAsync = (name: keyof IParameterViewState, value: any): Promise => { 70 | return new Promise((resolve, reject) => { 71 | const o: object = {} 72 | o[name] = value; 73 | this.setState(o, () => { 74 | resolve(); 75 | }); 76 | 77 | }); 78 | } 79 | 80 | // 81 | // this is the callback from the model...if the App changes the data 82 | // (e.g. picks a short name), then the model calls here. 83 | // e.g. this flow has to work: 84 | // 1. user types in long-paramter and hits TAB => onBlur is called 85 | // 2. this updates the model (model.longParameter) => event fired to all subscribers 86 | // 3. the model tries to find a reasonable shortParameter and variable name 87 | // 4. ...which results in this onPropertyChanged callback being called, and the UI needs to update 88 | public onPropertyChanged = async (model: ParameterModel, name: keyof IParameterViewState) => { 89 | await this.setStateAsync(name, model[name]); 90 | } 91 | 92 | // 93 | // when we blur we update the model with whatever the user typed 94 | private onBlur = async (e: React.FocusEvent) => { 95 | const key = e.currentTarget.id; 96 | if (key !== undefined) { 97 | this.state.Model[key] = e.currentTarget.value; 98 | } 99 | } 100 | // 101 | // for the checkboxes we update both the the model, which then gets a callback 102 | // where we update the internal state, which will then call render() 103 | private requiresInputStringChanged = (e: { originalEvent: Event, value: any, checked: boolean }): void => { 104 | this.state.Model.requiresInputString = e.checked; 105 | } 106 | 107 | private requiredParameterChanged = async (e: { originalEvent: Event, value: any, checked: boolean }): Promise => { 108 | this.state.Model.requiredParameter = e.checked; 109 | } 110 | // 111 | // this is for the input fields in the grid - we store the changes 112 | // in this component so that we follow the react "immutable state" rules 113 | // 114 | // we can also enforce some rules that are internal to the one parameter 115 | // 116 | // in onBlur, we update the model which will notify that the state has changed 117 | // 118 | private updateInputText = (e: React.FormEvent) => { 119 | const key: string = e.currentTarget.id; 120 | const value: string = e.currentTarget.value; 121 | const obj: object = {} 122 | obj[key] = value; 123 | this.setState(obj); 124 | } 125 | 126 | // 127 | // sets the focus when the user clicks inside the ParameterView 128 | // if it is a DIV or a FIELDSET, put the focus on longParameter 129 | public focusFirst = (e: any) => { 130 | 131 | if ((e.target.tagName === "DIV" || e.target.tagName === "FIELDSET") && !e.target.className.includes("p-checkbox")) { 132 | 133 | const el: any = e.currentTarget.querySelector("#longParameter"); 134 | if (el !== null) { 135 | el.focus(); 136 | } 137 | } 138 | } 139 | 140 | public render = () => { 141 | let fieldSetName: string = this.state.collapsed ? "parameter-fieldset parameter-fieldset-collapsed" : "parameter-fieldset"; 142 | 143 | return ( 144 |
145 | 146 |
221 | 222 | 223 | 224 | ) 225 | 226 | } 227 | 228 | } 229 | 230 | export default ParameterView; 231 | -------------------------------------------------------------------------------- /src/electron/main.ts: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | 3 | import { 4 | app, 5 | ipcMain, 6 | BrowserWindow, 7 | BrowserWindowConstructorOptions, 8 | Menu, 9 | MenuItem, 10 | MenuItemConstructorOptions, 11 | dialog, 12 | } from "electron"; 13 | import { IpcMainProxy } from "./ipcMainProxy"; 14 | import BashWizardMainService from "./bwMainService"; 15 | import path from "path"; 16 | import fs, { FSWatcher } from "fs"; 17 | import { IAsyncMessage, IConvertMessage, IOpenMessage } from "../Models/bwCommonModels" 18 | import windowStateKeeper from "electron-window-state" 19 | 20 | 21 | 22 | 23 | // Keep a global reference of the window object, if you don't, the window will 24 | // be closed automatically when the JavaScript object is garbage collected. 25 | export let g_mainWindow: BrowserWindow; 26 | let g_ipcMainProxy: IpcMainProxy; 27 | let g_bwService: BashWizardMainService; 28 | 29 | 30 | function disableHardwareAcceleration() { 31 | console.log('disabling hardware acceleration') 32 | app.disableHardwareAcceleration(); 33 | } 34 | 35 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'false'; // we do not load remote content -- only load from localhost 36 | // 37 | // this receives messages from the renderer to update the settings in the main app 38 | ipcMain.on('synchronous-message', (event: any, arg: any): any => { 39 | const menu: Menu | null = Menu.getApplicationMenu(); 40 | if (menu !== null) { 41 | console.log("changing autosave menu to " + Boolean(arg.autoSave)); 42 | menu.getMenuItemById("auto-save").checked = Boolean(arg.autoSave); 43 | event.returnValue = true; 44 | return; 45 | } 46 | event.returnValue = false; 47 | }) 48 | 49 | let lastFileNotificationTime: number = new Date().getTime(); 50 | let myFileWatcher: FSWatcher; 51 | // 52 | // this receives messages from the renderer to update the settings in the main app 53 | // 54 | // arg should look like {eventyType: "watch" | "unwatch", fileName: string} 55 | ipcMain.on('asynchronous-message', (event: any, arg: any): any => { 56 | console.log(`asynchronous-message-data: ${JSON.stringify(arg)}`) 57 | 58 | 59 | if (arg.eventType === "watch") { 60 | console.log(`watching ${arg.fileName}`); 61 | myFileWatcher = fs.watch(arg.fileName, { persistent: false }, (eventType: string, name: string) => { 62 | const currentTime: number = new Date().getTime(); 63 | if (currentTime - lastFileNotificationTime < 100) { // unlikely the user saves twice in 100ms... 64 | console.log(`rejecting notification at time ${currentTime} from ${lastFileNotificationTime} (diff: ${currentTime - lastFileNotificationTime})`) 65 | return; 66 | } 67 | console.log("Firing message for %s", arg.fileName); 68 | lastFileNotificationTime = currentTime; 69 | const msgObj: IAsyncMessage = { fileName: name, event: "file-changed", type: eventType }; 70 | event.sender.send('asynchronous-reply', JSON.stringify(msgObj)); 71 | 72 | }); 73 | } else if (arg.eventType === "unwatch") { 74 | 75 | if (myFileWatcher === undefined || myFileWatcher === null) { 76 | console.log("myFileWatcher null || undefined"); 77 | return; 78 | } 79 | console.log(`un-watching ${arg.fileName}`); 80 | fs.unwatchFile(arg.fileName, myFileWatcher.listeners[0]); 81 | } else { 82 | console.log("ERROR: BAD MESSAGE TYPE IN MAIN.TS"); 83 | } 84 | }) 85 | 86 | 87 | 88 | 89 | function createWindow() { 90 | // and load the index.html of the app. 91 | let mainWindowState = windowStateKeeper({ 92 | defaultWidth: 1000, 93 | defaultHeight: 800 94 | }); 95 | 96 | // Create the window using the state information 97 | 98 | // Let us register listeners on the window, so we can update the state 99 | // automatically (the listeners will be removed when the window is closed) 100 | // and restore the maximized or full screen state 101 | 102 | const windowOptions: BrowserWindowConstructorOptions = { 103 | 'x': mainWindowState.x, 104 | 'y': mainWindowState.y, 105 | 'width': mainWindowState.width, 106 | 'height': mainWindowState.height, 107 | frame: process.platform === "darwin", 108 | darkTheme: true, 109 | icon: path.join(__dirname, "..src/images/app-icons/png/bashwizard_64x64.png") 110 | }; 111 | 112 | 113 | // Create the browser window. 114 | 115 | if (process.env.ELECTRON_START_URL) { 116 | // Disable web security to support loading in local file system resources 117 | // TODO: Look into defined local security policy 118 | windowOptions.webPreferences = { 119 | webSecurity: false 120 | }; 121 | g_mainWindow = new BrowserWindow(windowOptions); 122 | g_mainWindow.loadURL(process.env.ELECTRON_START_URL); 123 | mainWindowState.manage(g_mainWindow); 124 | } else { 125 | // When running in production mode or with static files use loadFile api vs. loadUrl api. 126 | g_mainWindow = new BrowserWindow(windowOptions); 127 | g_mainWindow.loadFile("build/index.html"); 128 | } 129 | 130 | // Emitted when the window is closed. 131 | g_mainWindow.on("closed", () => { 132 | // Dereference the window object, usually you would store windows 133 | // in an array if your app supports multi windows, this is the time 134 | // when you should delete the corresponding element. 135 | g_mainWindow.destroy(); 136 | }); 137 | 138 | 139 | 140 | registerContextMenu(g_mainWindow); 141 | 142 | 143 | createMainMenu(g_mainWindow, false); 144 | 145 | 146 | g_ipcMainProxy = new IpcMainProxy(ipcMain, g_mainWindow); 147 | g_ipcMainProxy.register("RELOAD_APP", onReloadApp); 148 | g_ipcMainProxy.register("TOGGLE_DEV_TOOLS", onToggleDevTools); 149 | g_bwService = new BashWizardMainService(g_mainWindow); 150 | g_ipcMainProxy.registerProxy("BashWizardMainService", g_bwService); 151 | 152 | } 153 | 154 | function onReloadApp() { 155 | g_mainWindow.reload(); 156 | return true; 157 | } 158 | 159 | export async function onToggleDevTools(sender: any, show: boolean) { 160 | if (show) { 161 | g_mainWindow.webContents.openDevTools(); 162 | } else { 163 | g_mainWindow.webContents.closeDevTools(); 164 | } 165 | console.log(`OnToggleDevTools [show=${show}`) 166 | 167 | await g_bwService.updateSetting({ showDebugger: show }); 168 | 169 | } 170 | 171 | function onNew(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): void { 172 | const options = { 173 | type: 'question', 174 | buttons: ['Yes', 'No'], 175 | defaultId: 2, 176 | title: 'Bash Wizard', 177 | message: 'Create a new Script?', 178 | }; 179 | 180 | dialog.showMessageBox(g_mainWindow, options, (response: number) => { 181 | if (response === 0) { 182 | g_mainWindow.webContents.send('on-new', ''); 183 | } 184 | }); 185 | 186 | } 187 | 188 | async function onOpen(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): Promise { 189 | 190 | 191 | const fileName: string = await g_bwService.getOpenFile("Bash Wizard", [{ extensions: ["sh"], name: "Bash Script" }]); 192 | 193 | if (fileName !== null && fileName !== "") { 194 | const contents: string = await g_bwService.readText(fileName); 195 | const msg: IOpenMessage = { 196 | fileName: fileName, 197 | contents: contents 198 | } 199 | g_mainWindow.webContents.send('on-open', msg); 200 | } 201 | } 202 | 203 | function onSave(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): void { 204 | g_mainWindow.webContents.send('on-save', ""); 205 | } 206 | 207 | function onSaveAs(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): void { 208 | g_mainWindow.webContents.send('on-save-as', ""); 209 | } 210 | async function onUpdateDirectory(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): Promise { 211 | 212 | const dir: string = await g_bwService.selectDirectory("Bash Wizard", [{ extensions: ["sh"], name: "Bash Script" }]); 213 | if (dir !== undefined && dir !== "" && dir !== null) { 214 | const files: string[] = fs.readdirSync(dir); 215 | files.forEach(async file => { 216 | if (file.endsWith(".sh")) { 217 | try { 218 | 219 | let fileToConvert: string = dir + "/" + file; 220 | fileToConvert = path.normalize(fileToConvert); 221 | console.log(`Reading file ${fileToConvert}`); 222 | const contents: string = await g_bwService.readText(fileToConvert); 223 | const msg: IConvertMessage = { 224 | fileName: file, 225 | script: contents, 226 | directory: dir, 227 | fullFile: fileToConvert 228 | }; 229 | g_mainWindow.webContents.send('on-convert', msg); 230 | } 231 | catch (e) { 232 | console.log(`error converting ${file} in ${dir} error: ${e}`) 233 | 234 | } 235 | } 236 | }) 237 | 238 | } 239 | } 240 | 241 | function onAutoSaveChecked(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): void { 242 | g_mainWindow.webContents.send('on-setting-changed', { autoSave: menuItem.checked }); 243 | 244 | } 245 | function onAlwaysLoadChecked(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): void { 246 | g_mainWindow.webContents.send('on-setting-changed', { autoUpdate: menuItem.checked }); 247 | 248 | } 249 | async function checkedShowDebugger(menuItem: MenuItem, browserWindow: BrowserWindow, event: Event): Promise { 250 | const show: boolean = menuItem.checked; 251 | console.log(`OnToggleDevTools [show=${show}`) 252 | 253 | await g_bwService.updateSetting({ showDebugger: show }); 254 | } 255 | 256 | function createMainMenu(browserWindow: BrowserWindow, autoSave: boolean): void { 257 | 258 | let template: MenuItemConstructorOptions[]; 259 | template = [ 260 | { 261 | label: "File", accelerator: "Alt+F", 262 | submenu: [ 263 | { label: "New...", accelerator: "CmdOrCtrl+N", click: onNew }, 264 | { label: "Open...", accelerator: "CmdOrCtrl+O", click: onOpen }, 265 | { label: "Save", accelerator: "CmdOrCtrl+S", click: onSave }, 266 | { label: "Save As...", accelerator: "CmdOrCtrl+SHIFT+S", click: onSaveAs }, 267 | { type: "separator" }, 268 | { label: "Auto Save", type: "checkbox", checked: autoSave, click: onAutoSaveChecked, id: "auto-save" }, 269 | { label: "Auto Load", type: "checkbox", checked: false, id: "auto-load", click: onAlwaysLoadChecked }, 270 | { type: "separator" }, 271 | { label: "Update Directory", click: onUpdateDirectory, id: "update-directory" }, 272 | { type: "separator" }, 273 | { role: "reload" }, 274 | { type: "separator" }, 275 | { role: "quit" } 276 | ] 277 | }, 278 | { 279 | label: "View", accelerator: "Alt+v", 280 | submenu: [ 281 | { role: "reload" }, 282 | { role: "forcereload" }, 283 | { role: "toggledevtools", label: "Show Dev Tools", type: "checkbox", checked: false, accelerator: "F12" }, 284 | { type: "separator" }, 285 | { role: "resetzoom" }, 286 | { role: "zoomin" }, 287 | { role: "zoomout" }, 288 | { type: "separator" }, 289 | { role: "togglefullscreen" } 290 | ] 291 | }, 292 | { 293 | role: "window", accelerator: "Alt+w", 294 | submenu: [{ role: "minimize" }, { role: "close" }] 295 | }, 296 | { 297 | role: "help", accelerator: "Alt+a", 298 | submenu: [ 299 | { 300 | label: "Learn More", 301 | click() { 302 | require("electron").shell.openExternal( 303 | "https://github.com/joelong01/BashWizard/blob/master/README.md" 304 | ); 305 | } 306 | } 307 | ] 308 | } 309 | ]; 310 | 311 | if (process.platform === "darwin") { 312 | template.unshift({ 313 | label: app.getName(), 314 | submenu: [ 315 | { role: "about" }, 316 | { type: "separator" }, 317 | { role: "services" }, 318 | { type: "separator" }, 319 | { role: "hide" }, 320 | { role: "hideothers" }, 321 | { role: "unhide" }, 322 | { type: "separator" }, 323 | { role: "quit" } 324 | ] 325 | }); 326 | 327 | // Window menu 328 | template[3].submenu = [ 329 | { role: "close" }, 330 | { role: "minimize" }, 331 | { role: "zoom" }, 332 | { type: "separator" }, 333 | { role: "front" } 334 | ]; 335 | } 336 | // console.log(`menuTemplate: ${JSON.stringify(template)}`); 337 | 338 | const menu = Menu.buildFromTemplate(template); 339 | 340 | Menu.setApplicationMenu(menu); 341 | 342 | 343 | 344 | } 345 | 346 | /** 347 | * Adds standard cut/copy/paste/etc context menu comments when right clicking input elements 348 | * @param browserWindow The browser window to apply the context-menu items 349 | */ 350 | function registerContextMenu(browserWindow: BrowserWindow): void { 351 | console.log("registerContextMenu"); 352 | const selectionMenu = Menu.buildFromTemplate([ 353 | { role: "copy", accelerator: "CmdOrCtrl+C" }, 354 | { type: "separator" }, 355 | { role: "selectall", accelerator: "CmdOrCtrl+A" } 356 | ]); 357 | 358 | const inputMenu = Menu.buildFromTemplate([ 359 | { role: "undo", accelerator: "CmdOrCtrl+Z" }, 360 | { role: "redo", accelerator: "CmdOrCtrl+Shift+Z" }, 361 | { type: "separator" }, 362 | { role: "cut", accelerator: "CmdOrCtrl+X" }, 363 | { role: "copy", accelerator: "CmdOrCtrl+C" }, 364 | { role: "paste", accelerator: "CmdOrCtrl+V" }, 365 | { type: "separator" }, 366 | { role: "selectall", accelerator: "CmdOrCtrl+A" } 367 | ]); 368 | 369 | browserWindow.webContents.on("context-menu", (e, props) => { 370 | const { selectionText, isEditable } = props; 371 | if (isEditable) { 372 | inputMenu.popup({ 373 | window: browserWindow 374 | }); 375 | } else if (selectionText && selectionText.trim() !== "") { 376 | selectionMenu.popup({ 377 | window: browserWindow 378 | }); 379 | } 380 | }); 381 | } 382 | 383 | // This method will be called when Electron has finished 384 | // initialization and is ready to create browser windows. 385 | // Some APIs can only be used after this event occurs. 386 | app.on("ready", () => { 387 | console.log("on Ready called") 388 | createWindow(); 389 | }); 390 | 391 | // Quit when all windows are closed. 392 | app.on("window-all-closed", () => { 393 | 394 | // On OS X it is common for applications and their menu bar 395 | // to stay active until the user quits explicitly with Cmd + Q 396 | if (process.platform !== "darwin") { 397 | app.quit(); 398 | } 399 | }); 400 | 401 | app.on("activate", () => { 402 | // On OS X it's common to re-create a window in the app when the 403 | // dock icon is clicked and there are no other windows open. 404 | if (g_mainWindow === null) { 405 | createWindow(); 406 | } 407 | }); 408 | 409 | app.on("gpu-process-crashed", () => { 410 | console.error("GPU CRASHED!!") 411 | }); 412 | 413 | app.on("will-finish-launching", () => { 414 | console.log("will-finish-launching") 415 | disableHardwareAcceleration(); 416 | }) 417 | 418 | // In this file you can include the rest of your app's specific main process 419 | // code. You can also put them in separate files and require them here. 420 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------