Eugene Pankov 8 ani în urmă
comite
98fea7b102
69 a modificat fișierele cu 3066 adăugiri și 0 ștergeri
  1. 23 0
      .gitignore
  2. 108 0
      .gitlab-ci.yml
  3. 97 0
      Makefile
  4. 22 0
      README.md
  5. 58 0
      app/assets/bootstrap/bootstrap.less
  6. 4 0
      app/assets/bootstrap/include.less
  7. 541 0
      app/assets/bootstrap/overrides.less
  8. 141 0
      app/assets/bootstrap/variables.less
  9. BIN
      app/assets/img/disk.icns
  10. BIN
      app/assets/img/disk.ico
  11. BIN
      app/assets/img/icon.png
  12. BIN
      app/assets/img/logo.png
  13. 107 0
      app/assets/img/logo.svg
  14. BIN
      app/assets/img/shortcut.ico
  15. BIN
      app/assets/img/tray-darwin.png
  16. BIN
      app/assets/img/[email protected]
  17. BIN
      app/assets/img/tray-linux.png
  18. BIN
      app/assets/img/tray-win32.png
  19. BIN
      app/assets/img/user.png
  20. 23 0
      app/assets/toaster-custom.less
  21. 13 0
      app/index.pug
  22. 130 0
      app/main.js
  23. 14 0
      app/package.json
  24. 15 0
      app/src/app.d.ts
  25. 60 0
      app/src/app.module.ts
  26. 132 0
      app/src/components/app.less
  27. 19 0
      app/src/components/app.pug
  28. 87 0
      app/src/components/app.ts
  29. 53 0
      app/src/components/checkbox.less
  30. 4 0
      app/src/components/checkbox.pug
  31. 25 0
      app/src/components/checkbox.ts
  32. 69 0
      app/src/components/settingsModal.less
  33. 112 0
      app/src/components/settingsModal.pug
  34. 44 0
      app/src/components/settingsModal.ts
  35. 6 0
      app/src/entry.preload.ts
  36. 25 0
      app/src/entry.ts
  37. 176 0
      app/src/global.less
  38. 88 0
      app/src/services/config.ts
  39. 39 0
      app/src/services/electron.ts
  40. 69 0
      app/src/services/hostApp.ts
  41. 25 0
      app/src/services/log.ts
  42. 29 0
      app/src/services/modal.ts
  43. 48 0
      app/src/services/notify.ts
  44. 21 0
      app/src/services/quitter.ts
  45. 62 0
      build/config.gypi
  46. BIN
      build/icon.ico
  47. BIN
      build/icons/1024x1024.png
  48. BIN
      build/icons/128x128.png
  49. BIN
      build/icons/16x16.png
  50. BIN
      build/icons/256x256.png
  51. BIN
      build/icons/32x32.png
  52. BIN
      build/icons/512x512.png
  53. BIN
      build/icons/64x64.png
  54. BIN
      build/logo.png
  55. 35 0
      build/mac/Distribution.xml
  56. 20 0
      build/mac/Elements.component.plist
  57. 44 0
      build/mac/Elements.postinst.sh
  58. 50 0
      build/mac/Info.plist
  59. BIN
      build/mac/codesign
  60. BIN
      build/mac/icon.icns
  61. 34 0
      build/windows/build.wxs
  62. 84 0
      build/windows/elements.wxs
  63. BIN
      build/windows/signtool.exe
  64. 80 0
      build/windows/wix-theme.xml
  65. 76 0
      package.json
  66. 26 0
      tsconfig.json
  67. 22 0
      tslint.json
  68. 8 0
      typings.json
  69. 98 0
      webpack.config.js

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+app/node_modules
+app/src/daemonInstaller.generated.js
+app/assets/webpack
+app/elements-native.node
+
+node_modules
+typings
+
+build/files.wxs
+native/windows/build
+native/mac/build
+native/linux/build
+native/build
+dist
+driver/build
+driver/obj
+
+*.xcworkspacedata
+*.xcuserstate
+*.wixpdb
+
+coverage
+.nyc_output

+ 108 - 0
.gitlab-ci.yml

@@ -0,0 +1,108 @@
+cache:
+  untracked: true
+  key: "$CI_BUILD_REF_NAME"
+  paths:
+    - app/node_modules
+    - node_modules
+    - typings
+
+stages:
+    - Build
+    - Test
+    - Package
+    - Upload
+
+Build:
+  stage: Build
+  script:
+    - npm prune
+    - npm install
+    - cd app; npm prune && npm install; cd ..
+    - ./node_modules/.bin/typings install
+  tags:
+    - Linux
+  artifacts:
+    paths:
+      - node_modules
+      - typings
+      - app
+
+Test:
+  stage: Test
+  dependencies:
+    - Build
+  script:
+    - apt-get install -y xvfb libxtst6 libxss1 libgconf2-4 libnss3 libasound2
+    - xvfb-run -a make coverage
+  tags:
+    - Linux
+
+Windows package:
+  stage: Package
+  dependencies:
+    - Build
+  script:
+    - call npm install
+    - call npm install webpack # regenerate the .cmd launcher
+    - cd app
+    - call npm install
+    - cd ..
+    - call ./node_modules/.bin/webpack.cmd --progress
+    - call make package-windows
+    - call copy dist\Elements-Electron.exe Elements-Windows-%CI_BUILD_REF_NAME%.exe
+  artifacts:
+    name: Elements-Windows-%CI_BUILD_REF_NAME%
+    paths:
+    - Elements-Windows-%CI_BUILD_REF_NAME%.exe
+  tags:
+    - Windows
+
+macOS package:
+  stage: Package
+  dependencies:
+    - Build
+  script:
+    - npm install
+    - rm -rf node_modules/electron-macos-sign || true
+    - cp -r node_modules/electron-osx-sign node_modules/electron-macos-sign
+    - cd app; npm install; cd ..
+    - ./node_modules/.bin/webpack --progress
+    - security unlock-keychain -p rjvg login.keychain
+    - make package-mac
+    - cp dist/Elements-Electron.pkg ./Elements-macOS-$CI_BUILD_REF_NAME.pkg
+  artifacts:
+    name: Elements-macOS-$CI_BUILD_REF_NAME
+    paths:
+    - Elements-macOS-$CI_BUILD_REF_NAME.pkg
+  tags:
+    - macOS
+
+Linux package:
+  stage: Package
+  dependencies:
+    - Build
+  script:
+    - npm install
+    - cd app; npm install; cd ..
+    - ./node_modules/.bin/webpack --progress
+    - make build-linux
+    - cp dist/ELEMENTS*.AppImage ./Elements-Linux-$CI_BUILD_REF_NAME.AppImage
+  artifacts:
+    name: Elements-Linux-$CI_BUILD_REF_NAME
+    paths:
+    - Elements-Linux-$CI_BUILD_REF_NAME.AppImage
+  tags:
+    - Linux
+
+Upload packages:
+  stage: Upload
+  dependencies:
+    - Windows package
+    - macOS package
+    - Linux package
+  script:
+    - scp Elements-Windows-$CI_BUILD_REF_NAME.exe [email protected]:/mnt/elements/www/clients/
+    - scp Elements-macOS-$CI_BUILD_REF_NAME.pkg [email protected]:/mnt/elements/www/clients/
+    - scp Elements-Linux-$CI_BUILD_REF_NAME.AppImage [email protected]:/mnt/elements/www/clients/
+  tags:
+    - Local

+ 97 - 0
Makefile

@@ -0,0 +1,97 @@
+MAC_WS="/tmp/elements-build"
+MAC_OUTPUT="./dist/Elements-Electron.pkg"
+FULL_VERSION=$(shell python -c 'import subprocess; v = subprocess.check_output("git describe --tags --long", shell=True).strip()[1:]; print(v.split("-0-g")[0])')
+SHORT_VERSION=$(shell python -c 'import subprocess; v = subprocess.check_output("git describe --tags --long", shell=True).strip()[1:].split("-")[0]; print(v)')
+
+all: run
+
+run:
+	DEV=1 ./node_modules/.bin/electron ./app
+
+lint:
+	tslint -c tslint.json app/src/*.ts app/src/**/*.ts
+
+build:
+	DEV=1 ./node_modules/.bin/webpack --progress
+
+watch:
+	DEV=1 ./node_modules/.bin/webpack --progress -w
+
+build-native-windows:
+	echo :: Building native extensions
+	rm -r ./native/windows/build || true
+	cd native/windows && node-gyp rebuild --target=1.4.12 --arch=x64 --dist-url=https://atom.io/download/atom-shell
+	mkdir native/build || true
+	cp ./native/windows/build/Release/elements-native.node ./app/
+
+build-native-mac:
+	echo :: Building native extensions
+	rm -r ./native/mac/build || true
+	cd native/mac && node-gyp rebuild --target=1.4.12 --arch=x64 --dist-url=https://atom.io/download/atom-shell
+	mkdir native/build || true
+	cp ./native/mac/build/Release/elements-native.node ./app/
+
+build-native-linux:
+	echo :: Building native extensions
+	rm -r ./native/linux/build || true
+	cd native/linux && node-gyp rebuild --target=1.4.12 --arch=x64 --dist-url=https://atom.io/download/atom-shell
+	mkdir native/build || true
+	cp ./native/linux/build/Release/elements-native.node ./app/
+
+build-windows: build-native-windows
+	echo :: Building application
+	./node_modules/.bin/build --dir --win --em.version=$(FULL_VERSION)
+	cp ./app/assets/img/disk.ico dist/win-unpacked/
+
+build-mac: build-native-mac
+	echo :: Building application
+	./node_modules/.bin/build --dir --mac --em.version=$(FULL_VERSION)
+
+build-linux: build-native-linux
+	echo :: Building application
+	./node_modules/.bin/build --linux --em.version=$(FULL_VERSION)
+
+package-windows-app:
+	echo :: Building app MSI $(SHORT_VERSION)
+	heat dir dist/win-unpacked/ -cg Files -gg -scom -sreg -sfrag -srd -dr INSTALLDIR -var var.SourceDir -out build/files.wxs
+	candle -dSourceDir=dist\\win-unpacked -dProductVersion=$(SHORT_VERSION) -arch x64 -o dist/ build/files.wxs build/windows/elements.wxs
+	light -o dist/elements-app.msi dist/files.wixobj dist/elements.wixobj
+	build/windows/signtool.exe sign /f "build\\certificates\\Code Signing.p12" dist/elements-app.msi
+
+package-windows-bundle:
+	echo :: Building installer
+	candle -dVersion=$(SHORT_VERSION) -ext WixBalExtension -arch x64 -o dist/build.wixobj build/windows/build.wxs
+	light -ext WixBalExtension -o bundle.exe dist/build.wixobj
+	insignia -ib bundle.exe -o engine.exe
+	build/windows/signtool.exe sign /f "build\\certificates\\Code Signing.p12" engine.exe
+	insignia -ab engine.exe bundle.exe -o dist/Elements-Electron.exe
+	build/windows/signtool.exe sign /f "build\\certificates\\Code Signing.p12" dist/Elements-Electron.exe
+	rm engine.exe bundle.exe || true
+
+package-windows: build-windows package-windows-app package-windows-bundle
+
+package-mac: driver-mac build-mac
+	rm -rf $(MAC_WS) || true
+	mkdir -p $(MAC_WS)
+	mkdir -p $(MAC_WS)/app/Applications
+	mkdir -p $(MAC_WS)/driver/Library/Extensions
+	cp -Rv dist/mac/ELEMENTS.app $(MAC_WS)/app/Applications/
+	cp -Rv dist/ElementsDriver.kext $(MAC_WS)/driver/Library/Extensions
+	pkgbuild --root $(MAC_WS)/app \
+            --component-plist ./build/mac/Elements.component.plist \
+			--version $(SHORT_VERSION) \
+            --scripts ./build/mac \
+            $(MAC_WS)/Elements.pkg
+	pkgbuild --root $(MAC_WS)/driver \
+            --component-plist ./build/mac/ElementsDriver.component.plist \
+            --scripts ./build/mac \
+            $(MAC_WS)/ElementsDriver.pkg
+	cp ./build/mac/AFPTuner.pkg $(MAC_WS)/
+
+	productbuild --distribution "./build/mac/Distribution.xml"  \
+           --package-path $(MAC_WS) \
+		   --version $(SHORT_VERSION) \
+           --sign "Developer ID Installer: Syslink GmbH (V4JSMC46SY)" \
+           $(MAC_OUTPUT)
+
+.PHONY: run native build coverage

+ 22 - 0
README.md

@@ -0,0 +1,22 @@
+[![build status](http://ci.elements.tv/root/elements-benchmark/badges/master/build.svg)](http://ci.elements.tv/root/elements-benchmark/commits/master)
+
+This repository hosts the ELEMENTS Benchmark
+
+Build requirements:
+
+  * Windows: NodeJS, Windows SDK, WiX on $PATH, Cygwin with ``make`` on $PATH
+  * Mac: NodeJS, Xcode
+  * Linux: NodeJS
+  
+  
+Preparing:
+  
+  * ``npm install``
+  * ``typings install``
+  * ``webpack``
+  
+Building:
+    
+  * Windows: ``make package-windows`` → ``dist/Elements-Electron.exe``
+  * Mac: ``make package-mac`` → ``dist/Elements-Electron.pkg``
+  * Linux: ``make package-linux`` → ``dist/Elements-Electron.AppImage``

+ 58 - 0
app/assets/bootstrap/bootstrap.less

@@ -0,0 +1,58 @@
+// Core variables and mixins
+@import "../../../node_modules/bootstrap/less/variables.less";
+@import "variables.less";
+@import "../../../node_modules/bootstrap/less/mixins.less";
+//@import "mixin-overrides.less";
+
+// Reset and dependencies
+@import "../../../node_modules/bootstrap/less/normalize.less";
+@import "../../../node_modules/bootstrap/less/print.less";
+@import "../../../node_modules/bootstrap/less/glyphicons.less";
+
+// Core CSS
+@import "../../../node_modules/bootstrap/less/scaffolding.less";
+@import "../../../node_modules/bootstrap/less/type.less";
+@import "../../../node_modules/bootstrap/less/code.less";
+@import "../../../node_modules/bootstrap/less/grid.less";
+@import "../../../node_modules/bootstrap/less/tables.less";
+@import "../../../node_modules/bootstrap/less/forms.less";
+@import "../../../node_modules/bootstrap/less/buttons.less";
+
+// Components
+@import "../../../node_modules/bootstrap/less/component-animations.less";
+@import "../../../node_modules/bootstrap/less/dropdowns.less";
+@import "../../../node_modules/bootstrap/less/button-groups.less";
+@import "../../../node_modules/bootstrap/less/input-groups.less";
+@import "../../../node_modules/bootstrap/less/navs.less";
+@import "../../../node_modules/bootstrap/less/navbar.less";
+@import "../../../node_modules/bootstrap/less/breadcrumbs.less";
+@import "../../../node_modules/bootstrap/less/pagination.less";
+@import "../../../node_modules/bootstrap/less/pager.less";
+@import "../../../node_modules/bootstrap/less/labels.less";
+@import "../../../node_modules/bootstrap/less/badges.less";
+@import "../../../node_modules/bootstrap/less/jumbotron.less";
+@import "../../../node_modules/bootstrap/less/thumbnails.less";
+@import "../../../node_modules/bootstrap/less/alerts.less";
+@import "../../../node_modules/bootstrap/less/progress-bars.less";
+@import "../../../node_modules/bootstrap/less/media.less";
+@import "../../../node_modules/bootstrap/less/list-group.less";
+@import "../../../node_modules/bootstrap/less/panels.less";
+@import "../../../node_modules/bootstrap/less/responsive-embed.less";
+@import "../../../node_modules/bootstrap/less/wells.less";
+@import "../../../node_modules/bootstrap/less/close.less";
+
+// Components w/ JavaScript
+@import "../../../node_modules/bootstrap/less/modals.less";
+@import "../../../node_modules/bootstrap/less/tooltip.less";
+@import "../../../node_modules/bootstrap/less/popovers.less";
+@import "../../../node_modules/bootstrap/less/carousel.less";
+
+// Utility classes
+@import "../../../node_modules/bootstrap/less/utilities.less";
+@import "../../../node_modules/bootstrap/less/responsive-utilities.less";
+
+@import "overrides.less";
+
+body {
+    background: transparent;
+}

+ 4 - 0
app/assets/bootstrap/include.less

@@ -0,0 +1,4 @@
+// Core variables and mixins
+@import "../../../node_modules/bootstrap/less/variables.less";
+@import "variables.less";
+@import "../../../node_modules/bootstrap/less/mixins.less";

+ 541 - 0
app/assets/bootstrap/overrides.less

@@ -0,0 +1,541 @@
+@import (less, reference) "../../../node_modules/font-awesome/css/font-awesome.css";
+
+
+.glyphicon:extend(.fa all) {
+  &.glyphicon-chevron-right:extend(.fa-chevron-right all) {};
+  &.glyphicon-chevron-left:extend(.fa-chevron-left all) {};
+  &.glyphicon-chevron-up:extend(.fa-chevron-up all) {};
+  &.glyphicon-chevron-down:extend(.fa-chevron-down all) {};
+}
+
+h4 {
+    margin-bottom: 5px;
+}
+
+
+a {
+    color: #EFEAB1;
+    transition: opacity 0.125s, background 0.125s, color0.125s ;
+
+    &:hover {
+        color: #FFF79A;
+    }
+}
+
+
+.block-element {
+    display: block;
+}
+
+textarea {
+    resize: vertical;
+}
+
+.btn {
+    border: none !important;
+    box-shadow: 0 1px 1px rgba(0,0,0,.125);
+
+    &:active {
+        outline: 5px auto @brand-info;
+    }
+
+    &:active,
+    &.active {
+        .box-shadow(@control-shadow-active);
+    }
+
+    -webkit-transition: all 0.125s ease-out;
+    -moz-transition: all 0.125s ease-out;
+    -ms-transition: all 0.125s ease-out;
+    -o-transition: all 0.125s ease-out;
+    transition: all 0.125s ease-out;
+
+    &[disabled] {
+        cursor: default !important;
+    }
+}
+
+.btn-ink {
+    background: transparent !important;
+    box-shadow: none;
+
+    &.btn-danger {
+        color: #FF4832;
+    }
+
+    &.btn-xs {
+        padding-top: 0;
+        padding-bottom: 0;
+    }
+}
+
+.btn-toolbar {
+    margin: 0;
+
+    .btn, .btn-group, [uib-dropdown] {
+        float: none;
+    }
+
+    > .btn-group > * {
+        float: left;
+    }
+
+    > .btn, > .btn-group, > [uib-dropdown] {
+        margin: 0 5px 5px 0px;
+        vertical-align: top;
+
+        > .btn {
+            margin: 0;
+        }
+    }
+}
+
+@media (max-width: @screen-xs-max) {
+    .btn-toolbar-collapsible {
+        .btn {
+            font-size: 0;
+
+            .fa::before, .caret {
+                font-size: @font-size-base;
+            }
+        }
+    }
+}
+
+[uib-dropdown] {
+    position: relative;
+}
+
+[uib-dropdown-menu], .dropdown-menu {
+    .box-shadow(@control-dropdown-shadow);
+    top: 25px;
+
+    > li > * {
+        display: block;
+        padding: 5px 20px;
+        clear: both;
+        font-weight: normal;
+        line-height: @line-height-base;
+        color: @dropdown-link-color;
+        white-space: nowrap;
+
+        &[disabled] {
+            color: #888;
+            cursor: not-allowed;
+        }
+    }
+
+    > li.disabled a {
+        color: #666;
+        &:hover {
+            color: #666;
+        }
+    }
+}
+
+[uib-dropdown-menu] > .active > * {
+    &,
+    &:hover,
+    &:focus {
+        color: @dropdown-link-active-color;
+        text-decoration: none;
+        outline: 0;
+        background-color: @dropdown-link-active-bg;
+    }
+}
+
+
+.form-control {
+    border: none;
+
+    &[checkbox] {
+      border: none;
+      background: transparent;
+      display: inline-block;
+      margin: @padding-base-vertical 0 0;
+    }
+
+    border-radius: 0;
+    .box-shadow(~"0 1px 1px rgba(0, 0, 0, 0.51)");
+
+    &:focus {
+        .box-shadow(~"0 1px 1px rgba(0, 0, 0, 0.51)");
+    }
+
+    .transition(0.25s background);
+
+    &::-webkit-input-placeholder {
+        font-style: italic;
+    }
+}
+
+.input-group {
+    .box-shadow(~"0 1px 1px rgba(0, 0, 0, 0.51)");
+    .form-control {
+        .box-shadow(none);
+    }
+}
+
+.input-group-addon {
+    border: none;
+    padding: 6px 8px;
+}
+
+@media (min-width: @screen-sm-min) {
+    .control-label {
+        font-size: 12px;
+        padding-top: 10px;
+        font-weight: normal;
+    }
+}
+
+.form-group .control-label {
+    font-size: 12px;
+    padding-top: 10px;
+    font-weight: bold;
+    color: #777;
+}
+
+label.form-control-static {
+    font-size: inherit;
+}
+
+.input-group {
+    width: 100%;
+
+    *:focus + .input-group-addon,
+    *:focus + .input-group-btn {
+        //border-bottom: 1px solid @brand-primary;
+    }
+
+    .input-group-addon, .input-group-btn {
+        border: none;
+
+        &.input-sm {
+            border-radius: 0;
+        }
+
+        a {
+            margin: 0 !important;
+        }
+
+        text-decoration: none !important;
+    }
+}
+
+.label {
+    font-size: 76%;
+    position: relative;
+    padding: 5px 4px 4px;
+}
+
+.list-group {
+    .box-shadow(@control-shadow);
+}
+
+.list-group-item {
+    border: none;
+    border-top: 1px solid @list-group-line-border;
+    cursor: default;
+
+    &:first-child {
+        border-top: none;
+    }
+
+    &.active {
+        color: #f7e61d;
+        border-right: 2px solid #f7e61d;
+    }
+}
+
+.list-group-item.combi {
+    padding: 0;
+    clear: both;
+
+    tr& {
+        a.main, .btn {
+            display: table-cell !important;
+        }
+    }
+
+    & > .main {
+        display: block;
+
+        h4 {
+            margin-top: 4px;
+        }
+
+        pre {
+            background: transparent;
+            margin: 0;
+        }
+
+        video-thumbnail, asset-thumbnail {
+            margin-right: 15px;
+        }
+    }
+
+    & > a.main {
+        cursor: pointer;
+        color: inherit;
+    }
+
+    & > a.main, & > a.btn, & > button, & > [uib-dropdown] > button {
+        &:hover,
+        &:focus {
+            text-decoration: none;
+            color: @list-group-link-hover-color;
+            background-color: @list-group-hover-bg;
+        }
+    }
+
+    & > a.btn, & > button, & > [uib-dropdown] > button {
+        background-color: @list-group-bg;
+        position: relative; // more z-index
+        z-index: 2;
+        display: block;
+        float: right;
+        border: none;
+        margin: 0;
+        box-shadow: none;
+    }
+
+    & > .drag-handle {
+        float: left;
+        cursor: move;
+    }
+
+    &.single > .main {
+        height: 40px;
+        line-height: 39px;
+        padding: 0 15px;
+
+        [checkbox] {
+            display: inline;
+        }
+
+        .label-lg {
+            top: 7px;
+        }
+    }
+
+    &.double > .main {
+        height: 68px;
+        line-height: 25px;
+        padding: 10px 15px;
+    }
+
+    &.single {
+        a.btn, button, .drag-handle {
+            padding: 10px 12px;
+        }
+    }
+
+    &.double {
+        a.btn, button, .drag-handle {
+            padding: 24px;
+        }
+    }
+}
+
+
+.form-control-focus(@color: @input-border-focus) {
+    @color-rgba: rgba(red(@color), green(@color), blue(@color), .3);
+}
+
+.modal {
+    background-color: rgba(0,0,0,.5);
+    position: fixed !important;
+
+
+    .modal-dialog {
+        margin: 0 auto;
+        top: 50px;
+
+        .modal-content {
+            background-color: @body-bg;
+
+
+            .modal-body {
+                max-height: 80vh;
+                overflow-y: auto;
+                padding: 25px 15px;
+                -webkit-app-region: no-drag;
+            }
+
+            .modal-footer {
+                border-top: 1px solid #222;
+                padding: 0;
+                -webkit-app-region: no-drag;
+
+                .btn.btn-default {
+                    background: @input-bg;
+                    border: none !important;
+                    cursor: pointer;
+                    box-shadow: none !important;
+                    padding: 10px;
+
+                    &:hover {
+                        background: rgba(0,0,0,.25);
+                    }
+
+                    &:active {
+                        background: rgba(0,0,0,.5);
+                    }
+                }
+            }
+        }
+    }
+}
+
+.modal-backdrop {
+    background: rgba(0,0,0,.25);
+}
+
+
+.navbar-fixed-top {
+    border-width: 0 0 2px;
+    height: 52px;
+}
+
+.navbar-form {
+    box-shadow: none !important;
+    border: none !important;
+}
+
+.nav-tabs-justified,
+.nav-tabs  {
+    border-bottom: 1px solid @nav-tabs-border-color;
+
+    > li {
+        > a {
+            border: 1px solid transparent;
+            border-radius: 0;
+            color: @text-color;
+            &:hover {
+                background-color: @nav-tabs-active-link-hover-bg;
+                border-bottom: 1px solid @nav-tabs-border-color;
+            }
+        }
+
+        // Active state, and its :hover to override normal :hover
+        &.active > a {
+            &,
+            &:hover,
+            &:focus {
+                border-radius: 0;
+                border: 1px solid transparent;
+                border-bottom: 1px solid @nav-tabs-active-link-hover-border-color;
+            }
+        }
+    }
+}
+
+.nav-justified {
+    > li {
+        display: table-cell;
+        width: 1%;
+        > a {
+            margin-bottom: 0;
+        }
+    }
+}
+
+
+.popover-content {
+    //padding: 0;
+}
+
+.progress {
+    margin-bottom: 0;
+}
+
+
+table {
+    background: transparent;
+
+    td {
+        vertical-align: top;
+    }
+
+    .table-condensed {
+        display: block;
+    }
+}
+
+.table {
+    background: @table-bg;
+    .box-shadow(@control-shadow);
+    &.no-margin {
+        margin-bottom: 0;
+    }
+
+    > thead,
+    > tbody,
+    > tfoot {
+        > tr {
+            background-color: transparent;
+            > th,
+            > td {
+                border-top: 1px solid @table-line-border-color;
+            }
+
+            &:first-child {
+                th, td {
+                    border-top: none;
+                }
+            }
+        }
+    }
+    > thead > tr > th {
+        border-bottom: 2px solid @table-line-border-color;
+    }
+}
+
+.table-bordered {
+    > thead,
+    > tbody,
+    > tfoot {
+        > tr {
+            > th,
+            > td {
+                border: 1px solid @table-line-border-color;
+            }
+        }
+    }
+}
+
+
+.help-block {
+  color: darken(@text-color, 10%);
+}
+
+
+.label {
+    margin-right: 5px;
+}
+
+
+.datepicker-dropdown {
+    &.datepicker-orient-top:before {
+        display: none !important;
+    }
+
+    &.datepicker-orient-top:after {
+        border-bottom: 7px solid @dropdown-bg !important;
+    }
+
+    &.datepicker-orient-bottom:after {
+        border-top: 7px solid @dropdown-bg !important;
+    }
+
+    table tr td.day:hover  {
+        background: #555 !important;
+    }
+
+    table tr td.active {
+        background: @brand-primary !important;
+        color: #222 !important;
+        text-shadow: none !important;
+    }
+}

+ 141 - 0
app/assets/bootstrap/variables.less

@@ -0,0 +1,141 @@
+@brand-primary:         #f7e61d;
+@brand-success:         #5cb85c;
+@brand-info:            #5bc0de;
+@brand-warning:         #f0ad4e;
+@brand-danger:          #FF1C01;
+
+// New
+@brand-primary:         #f7e61d;
+@brand-success:         #42B500;
+@brand-info:            #01BAEF;
+@brand-warning:         #DB8A00;
+@brand-danger:          #EF2F00;
+
+
+@control-shadow: 0 1px 1px rgba(0,0,0,.25);
+@control-shadow-active: 0 1px 1px rgba(0,0,0,.25) inset, @control-shadow;
+@control-dropdown-shadow: 0 0 50px rgba(0,0,0,.5),  @control-shadow;
+@form-accent: #DBCA00;
+@form-accent-bright: @brand-primary;
+
+
+@body-bg:               #1D272D;
+@text-color:            #aaa;
+
+@font-family-sans-serif:  "Source Sans Pro", "PT Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+@icon-font-path:          "../fonts/";
+@component-active-color:        rgba(0,0,0,.15);
+@component-active-color:        darken(@component-active-bg, 30%);
+@table-bg:                      #444;
+@table-bg-accent:               rgba(255,255,255,.15);
+@table-bg-hover:                #666;
+@table-border-color:            #2e2e2e;
+@table-line-border-color:       #4f4f4f;
+
+@btn-default-color:              @text-color;
+@btn-default-bg:                 #243D49;
+@btn-default-border:             transparent;
+@btn-primary-color:              @component-active-color;
+@btn-primary-border:             #584E00;
+@btn-danger-border:              rgba(0,0,0,.5);
+@btn-danger-color:               white;
+@btn-danger-bg:                  #FF4630;
+@btn-danger-border:              transparent;//@brand-danger;
+@btn-link-disabled-color:        darken(@text-color, 20%);
+
+@input-bg:                       #11181C;
+@input-bg-disabled:              #2a2f31;
+@input-color:                    #bbb;
+@input-border:                   #3a3a3a;
+@input-group-addon-border-color: @input-bg;
+@input-color-placeholder:        #777;
+@input-group-addon-bg:           @input-bg;
+
+@dropdown-bg:                    rgba(64,64,64,.95); //@body-bg;
+@dropdown-link-color:            @text-color;
+@dropdown-link-hover-color:      #ddd;
+@dropdown-link-hover-bg:         #444;
+@dropdown-link-disabled-color:   darken(@text-color, 5%);
+@navbar-default-bg:                #23272A;
+@navbar-default-border:            #111;
+
+
+@nav-tabs-border-color: #666;
+@nav-tabs-link-hover-border-color: transparent;
+
+@nav-tabs-active-link-hover-bg:             rgba(255,255,255,.1);
+@nav-tabs-active-link-hover-color:          @brand-primary;
+@nav-tabs-active-link-border-color:         @brand-primary;
+@nav-tabs-active-link-hover-border-color:   @brand-primary;
+
+
+@pagination-color:                     @btn-default-color;
+@pagination-bg:                        @btn-default-bg;
+@pagination-border:                    @btn-default-border;
+
+@pagination-hover-color:               @btn-default-color;
+@pagination-hover-bg:                  lighten(@btn-default-bg, 5%);
+@pagination-hover-border:              @btn-default-border;
+
+@pagination-active-color:              @brand-primary;
+@pagination-active-bg:                 darken(@btn-default-bg, 5%);
+@pagination-active-border:             @btn-default-border;
+
+@pagination-disabled-color:            @btn-link-disabled-color;
+@pagination-disabled-bg:               darken(@btn-default-bg, 5%);
+@pagination-disabled-border:           @btn-default-border;
+//@state-success-bg:               #234116; //#dff0d8;
+//@state-info-bg:                  #0C3A50; //#d9edf7;
+//@state-danger-bg:                #9E3B3B;///#f2dede;
+@popover-bg:                          rgba(64,64,64,.95);
+@popover-arrow-color:                 #444;
+@progress-bg:                 #555;
+@list-group-bg:                 rgba(255,255,255,.1);
+@list-group-disabled-bg:        #333;
+@list-group-border:             transparent;
+@list-group-line-border:        rgba(255,255,255,.1);
+@list-group-hover-bg:           rgba(255,255,255,.2);
+@list-group-link-color:         @text-color;
+@list-group-link-heading-color: @text-color;
+@list-group-active-bg:          rgba(255,255,255,.3);
+@list-group-active-border:      transparent;
+
+
+@panel-bg:                    @table-bg;
+@panel-inner-border:          @table-border-color;
+@panel-footer-bg:             #666;
+@panel-default-text:          #ddd;
+@panel-default-border:        @table-border-color;
+@panel-default-heading-bg:    #666;
+@well-bg:                     #222;
+@badge-bg:                    #333;
+@badge-active-bg:             #333;
+@code-bg:                     #222;
+@pre-bg:                      #222;
+@pre-color:                   #bbb;
+@blockquote-border-color:     #444;
+@page-header-border-color:    #444;
+
+
+@alert-bg: #2A2A2A;
+
+@state-success-text:             @brand-success;
+@state-success-bg:               @alert-bg;
+@state-success-border:           @state-success-text;
+
+@state-info-text:                @brand-info;
+@state-info-bg:                  @alert-bg;
+@state-info-border:              @state-info-text;
+
+@state-warning-text:             #F27208;
+@state-warning-bg:               @alert-bg;
+@state-warning-border:           @state-warning-text;
+
+@state-danger-text:              @brand-danger;
+@state-danger-bg:                @alert-bg;
+@state-danger-border:            @state-danger-text;
+
+
+@border-radius-base:        1px;
+@border-radius-large:       3px;
+@border-radius-small:       1px;

BIN
app/assets/img/disk.icns


BIN
app/assets/img/disk.ico


BIN
app/assets/img/icon.png


BIN
app/assets/img/logo.png


+ 107 - 0
app/assets/img/logo.svg

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   xml:space="preserve"
+   width="536.82501"
+   height="126.525"
+   viewBox="0 0 536.82501 126.525"
+   sodipodi:docname="elements_wortmarke+bildmarke_gelb+weiß_rz.svg"
+   inkscape:export-filename="/home/eugene/Downloads/logo.png"
+   inkscape:export-xdpi="42.677204"
+   inkscape:export-ydpi="42.677204"><metadata
+     id="metadata8"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+     id="defs6" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1845"
+     inkscape:window-height="1025"
+     id="namedview4"
+     showgrid="false"
+     inkscape:zoom="1.1586643"
+     inkscape:cx="143.54613"
+     inkscape:cy="-3.8338411"
+     inkscape:window-x="75"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g10" /><g
+     id="g10"
+     inkscape:groupmode="layer"
+     inkscape:label="ink_ext_XXXXXX"
+     transform="matrix(1.25,0,0,-1.25,0,126.525)"><g
+       id="g12"
+       transform="scale(0.1,0.1)"><path
+         d="m 202.457,809.793 404.891,0 0,202.457 -404.891,0 0,-202.457 z"
+         style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path14"
+         inkscape:connector-curvature="0" /><path
+         d="m 0,607.375 202.445,0 0,202.457 -202.445,0 0,-202.457 z"
+         style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path16"
+         inkscape:connector-curvature="0" /><path
+         d="m 202.457,404.918 404.891,0 0,202.457 -404.891,0 0,-202.457 z"
+         style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path18"
+         inkscape:connector-curvature="0" /><path
+         d="m 0,202.461 202.445,0 0,202.457 -202.445,0 0,-202.457 z"
+         style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path20"
+         inkscape:connector-curvature="0" /><path
+         d="m 202.457,0 404.891,0 0,202.461 -404.891,0 0,-202.461 z"
+         style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path22"
+         inkscape:connector-curvature="0" /><path
+         d="m 1072.96,482.414 -148.108,0 0,48.219 148.108,0 0,-48.219 z m 39.27,-234.23 -302.441,0 0,516.679 302.441,0 0,-48.218 -247.32,0 0,-420.243 247.32,0 0,-48.218"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path24"
+         inkscape:connector-curvature="0" /><path
+         d="m 1518.04,248.184 -187.4,0 0,48.218 187.4,0 0,-48.218 z m -247.34,0 -55.1,0 0,516.679 55.1,0 0,-516.679"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path26"
+         inkscape:connector-curvature="0" /><path
+         d="m 1875.61,482.414 -148.12,0 0,48.219 148.12,0 0,-48.219 z m 39.27,-234.23 -302.43,0 0,516.679 302.43,0 0,-48.218 -247.33,0 0,-420.243 247.33,0 0,-48.218"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path28"
+         inkscape:connector-curvature="0" /><path
+         d="m 2515.65,248.184 -55.12,0 0,333.425 55.12,0 0,-333.425 z m -432.66,0 -55.1,0 0,333.425 55.1,0 0,-333.425"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path30"
+         inkscape:connector-curvature="0" /><path
+         d="m 2899.41,482.414 -148.12,0 0,48.219 148.12,0 0,-48.219 z m 39.28,-234.23 -302.44,0 0,516.679 302.44,0 0,-48.218 -247.34,0 0,-420.243 247.34,0 0,-48.218"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path32"
+         inkscape:connector-curvature="0" /><path
+         d="m 3438.17,419.605 -55.11,0 0,345.258 55.11,0 0,-345.258 z m -334.12,-171.421 -55.12,0 0,344.457 55.12,0 0,-344.457 z m 336.1,19.675 -44.09,-28.949 -352.46,505.524 44.79,28.933 351.76,-505.508"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path34"
+         inkscape:connector-curvature="0" /><path
+         d="m 3732.38,248.184 -55.1,0 0,408.523 55.1,0 0,-408.523 z m 160.52,468.461 -376.14,0 0,48.218 376.14,0 0,-48.218"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path36"
+         inkscape:connector-curvature="0" /><path
+         d="m 4243.6,638.805 c -18.61,62.683 -73.02,92.308 -126.76,92.308 -56.49,0 -108.85,-33.754 -108.85,-95.761 0,-37.891 23.43,-75.786 73.02,-86.801 l 16.54,-4.141 -13.1,-48.91 -15.16,3.453 c -75.77,17.902 -115.04,76.461 -115.04,135.02 0,95.757 80.6,144.683 162.59,144.683 73.71,0 148.81,-39.273 171.54,-122.64 L 4243.6,638.805 Z M 4122.34,234.406 c -75.78,0 -153.63,38.571 -181.18,124.696 l 46.84,19.285 c 19.99,-66.137 75.79,-95.762 132.27,-95.762 63.39,0 121.26,37.203 121.26,104.027 0,46.856 -28.24,81.297 -83.37,92.313 l -13.77,2.758 13.08,48.91 19.99,-4.129 c 76.46,-15.848 117.12,-69.586 117.12,-137.777 0,-85.438 -68.9,-154.321 -172.24,-154.321"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path38"
+         inkscape:connector-curvature="0" /><path
+         d="m 2528.61,742.93 -44.08,35.597 -211.89,-224.117 -212.58,224.731 -44.08,-35.598 249.15,-263.246 7.51,-7.883 7.51,7.883 248.46,262.633"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path40"
+         inkscape:connector-curvature="0" /></g></g></svg>

BIN
app/assets/img/shortcut.ico


BIN
app/assets/img/tray-darwin.png



BIN
app/assets/img/tray-linux.png


BIN
app/assets/img/tray-win32.png


BIN
app/assets/img/user.png


+ 23 - 0
app/assets/toaster-custom.less

@@ -0,0 +1,23 @@
+app > toaster-container > #toast-container {
+    width: 100% !important;
+    left: 0 !important;
+    right: 0 !important;
+    top: 50px !important;
+
+    .toast {
+        box-shadow: none !important;
+        text-shadow: none !important;
+        opacity: 1 !important;
+        border-radius: 0 !important;
+        width: 100% !important;
+        padding: 10px !important;
+
+        .toaster-icon {
+            display: none !important;
+        }
+
+        .toast-title {
+            font-weight: normal !important;
+        }
+    }
+}

+ 13 - 0
app/index.pug

@@ -0,0 +1,13 @@
+doctype html
+html
+    head
+        meta(charset='UTF-8')
+        title ELEMENTS Benchmark
+        base(href='index.html')
+        script.
+            console.timeStamp('index')
+            window.nodeRequire = require
+        script(src='./preload.js')
+        script(src='./bundle.js', defer)
+    body(style='background-image: radial-gradient(circle, #2b3840 0%, #1D272D 100%); min-height: 100vh')
+        app

+ 130 - 0
app/main.js

@@ -0,0 +1,130 @@
+const Config = require('electron-config')
+const electron = require('electron')
+const platform = require('os').platform()
+require('electron-debug')({enabled: true, showDevTools: process.argv.indexOf('--debug') != -1})
+
+let app = electron.app
+let windowConfig = new Config({name: 'window'})
+
+
+setupWindowManagement = () => {
+    let windowCloseable
+
+    app.window.on('close', (e) => {
+        windowConfig.set('windowBoundaries', app.window.getBounds())
+        if (!windowCloseable) {
+            app.window.hide()
+            e.preventDefault()
+        }
+    })
+
+    app.window.on('closed', () => {
+        app.window = null
+    })
+
+    electron.ipcMain.on('window-closeable', (event, flag) => {
+        windowCloseable = flag
+    })
+
+    electron.ipcMain.on('window-focus', () => {
+        app.window.show()
+        app.window.focus()
+    })
+
+    app.on('before-quit', () => windowCloseable = true)
+}
+
+
+setupMenu = () => {
+    var template = [{
+        label: "Application",
+        submenu: [
+            { type: "separator" },
+            { label: "Quit", accelerator: "CmdOrCtrl+Q", click: () => {
+                app.window.webContents.send('host:quit-request')
+            }}
+        ]}, {
+            label: "Edit",
+            submenu: [
+                { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
+                { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
+                { type: "separator" },
+                { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
+                { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
+                { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
+                { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
+            ]
+        }]
+
+    electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template))
+}
+
+
+start = () => {
+    let t0 = Date.now()
+
+    let secondInstance = app.makeSingleInstance((argv) => {
+        app.window.focus()
+    })
+
+    if (secondInstance) {
+        app.quit()
+        return
+    }
+
+    let options = {
+        width: 800,
+        height: 400,
+        icon: `${app.getAppPath()}/assets/img/icon.png`,
+        title: 'ELEMENTS Benchmark',
+        minWidth: 800,
+        minHeight: 400,
+        'web-preferences': {'web-security': false},
+        //- background to avoid the flash of unstyled window
+        backgroundColor: '#1D272D',
+    }
+    Object.assign(options, windowConfig.get('windowBoundaries'))
+
+    if (platform == 'darwin') {
+        options.titleBarStyle = 'hidden'
+    } else {
+        options.frame = false
+    }
+
+    app.commandLine.appendSwitch('--disable-http-cache')
+
+    app.window = new electron.BrowserWindow(options)
+    app.window.loadURL(`file://${app.getAppPath()}/assets/webpack/index.html`, {extraHeaders: "pragma: no-cache\n"})
+
+    if (platform != 'darwin') {
+        app.window.setMenu(null)
+    }
+
+    app.window.show()
+    app.window.focus()
+
+    setupWindowManagement()
+    setupMenu()
+
+    console.info(`Host startup: ${Date.now() - t0}ms`)
+    t0 = Date.now()
+    electron.ipcMain.on('app:ready', () => {
+        console.info(`App startup: ${Date.now() - t0}ms`)
+    })
+}
+
+app.on('ready', start)
+
+app.on('activate', () => {
+    if (!app.window)
+        start()
+    else {
+        app.window.show()
+        app.window.focus()
+    }
+})
+
+process.on('uncaughtException', function(err) {
+    console.log(err)
+    app.window.webContents.send('uncaughtException', err)
+})

+ 14 - 0
app/package.json

@@ -0,0 +1,14 @@
+{
+  "name": "term",
+  "version": "1.0.0",
+  "main": "main.js",
+  "dependencies": {
+    "child-process-promise": "^2.1.3",
+    "devtron": "^1.4.0",
+    "electron-config": "^0.2.1",
+    "electron-debug": "^1.0.1",
+    "electron-is-dev": "^0.1.2",
+    "path": "^0.12.7",
+    "pty.js": "https://github.com/Tyriar/pty.js/tarball/c75c2dcb6dcad83b0cb3ef2ae42d0448fb912642"
+  }
+}

+ 15 - 0
app/src/app.d.ts

@@ -0,0 +1,15 @@
+declare var nodeRequire: any
+interface IPromise {}
+
+declare interface Window {
+    require: any
+    process: any
+    __dirname: any
+    __platform: any
+}
+
+declare var window: Window
+
+declare interface Console {
+    timeStamp(...args: any[])
+}

+ 60 - 0
app/src/app.module.ts

@@ -0,0 +1,60 @@
+import { NgModule } from '@angular/core'
+import { BrowserModule } from '@angular/platform-browser'
+import { HttpModule } from '@angular/http'
+import { FormsModule } from '@angular/forms'
+import { ToasterModule } from 'angular2-toaster'
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
+import { PerfectScrollbarModule } from 'angular2-perfect-scrollbar'
+import { PerfectScrollbarConfigInterface } from 'angular2-perfect-scrollbar'
+
+const PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {
+  suppressScrollX: true
+}
+
+
+import { ConfigService } from 'services/config'
+import { ElectronService } from 'services/electron'
+import { HostAppService } from 'services/hostApp'
+import { LogService } from 'services/log'
+import { ModalService } from 'services/modal'
+import { NotifyService } from 'services/notify'
+import { QuitterService } from 'services/quitter'
+import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
+
+import { AppComponent } from 'components/app'
+import { CheckboxComponent } from 'components/checkbox'
+import { SettingsModalComponent } from 'components/settingsModal'
+
+
+@NgModule({
+    imports: [
+        BrowserModule,
+        HttpModule,
+        FormsModule,
+        ToasterModule,
+        NgbModule.forRoot(),
+        PerfectScrollbarModule.forRoot(PERFECT_SCROLLBAR_CONFIG),
+    ],
+    providers: [
+        ConfigService,
+        ElectronService,
+        HostAppService,
+        LogService,
+        ModalService,
+        NotifyService,
+        QuitterService,
+        LocalStorageService,
+    ],
+    entryComponents: [
+        SettingsModalComponent,
+    ],
+    declarations: [
+        AppComponent,
+        CheckboxComponent,
+        SettingsModalComponent,
+    ],
+    bootstrap: [
+        AppComponent
+    ]
+})
+export class AppModule {}

+ 132 - 0
app/src/components/app.less

@@ -0,0 +1,132 @@
+@import "~bootstrap/less/variables.less";
+@import "~bootstrap/variables.less";
+
+:host {
+    display: flex;
+    width: 100vw;
+    height: 100vh;
+    flex-direction: column;
+    overflow: hidden;
+    -webkit-user-select: none;
+    -webkit-font-smoothing: antialiased;
+    background: @body-bg;
+}
+
+
+.navbar {
+    flex: none;
+    border-left: none;
+    border-right: none;
+    border-top: none;
+    -webkit-app-region: drag;
+    margin: 0;
+
+    background: @navbar-default-bg;
+    :host.platform-darwin & {
+        background: fade(@navbar-default-bg, 50%);
+    }
+
+    .navbar-btn-big {
+        margin: 0;
+        padding: 15px 20px 15px;
+        background-color: #333;
+        text-align: left;
+
+        background: transparent;
+        box-shadow: none;
+
+        &.btn-profile {
+            padding: 7px 14px 7px;
+            display: flex;
+            flex-direction: row;
+            max-width: 150px;
+
+            :host.platform-darwin & {
+                max-width: 120px;
+            }
+
+            >img {
+                flex: none;
+                float: left;
+                margin: 2px 10px 4px 0;
+                width: 30px;
+                height: 30px;
+
+                :host.platform-darwin & {
+                    margin-right: 0;
+                }
+            }
+
+            >div {
+                flex: auto;
+
+                :host.platform-darwin & {
+                    display: none;
+                }
+
+                .username {
+                }
+
+                .settings {
+                    font-size: 10px;
+                }
+            }
+        }
+
+        &.btn-close {
+            font-size: 38px;
+            padding: 1px 13px 2px;
+            line-height: 47px;
+
+            :host.platform-darwin & {
+                display: none;
+            }
+        }
+    }
+
+    button.navbar-btn-big:hover {
+        background-color: #222;
+    }
+
+    button.navbar-btn-big:active {
+        background-color: #111;
+    }
+
+
+    .navbar-brand {
+        padding: 11px 15px;
+        width: 150px;
+
+        :host.platform-darwin & {
+            display: block;
+            margin: auto;
+            float: none;
+        }
+
+        img {
+            height: 28px;
+        }
+    }
+}
+
+[scrollable] {
+    width: 100vw;
+    flex: auto;
+    display: flex;
+
+    .nano {
+        flex: auto;
+        height: auto;
+    }
+}
+
+footer {
+  display: block;
+  flex: none;
+  height: 1px;
+}
+
+perfect-scrollbar {
+    flex: auto;
+    min-height: 0;
+}

+ 19 - 0
app/src/components/app.pug

@@ -0,0 +1,19 @@
+div.navbar.navbar-default.draggable
+    button.btn.btn-default.navbar-btn.navbar-btn-big.btn-close.pull-right((click)='hide()', title='Hide')
+        | &times;
+    button.btn.btn-default.navbar-btn.navbar-btn-big.pull-right((click)='showSettings()', title='Settings')
+        i.fa.fa-cog
+    div.navbar-brand
+        img.logo(src=require("img/logo.svg"))
+
+perfect-scrollbar
+    div.container
+        div#term(style='width: 300px; height: 300px;')
+
+
+footer
+
+toaster-container([toasterconfig]="toasterconfig")
+template(ngbModalContainer)
+
+div.window-resizer.window-resizer-tl

+ 87 - 0
app/src/components/app.ts

@@ -0,0 +1,87 @@
+import { Component, ElementRef } from '@angular/core'
+import { ModalService } from 'services/modal'
+import { ElectronService } from 'services/electron'
+import { HostAppService } from 'services/hostApp'
+import { LogService } from 'services/log'
+import { QuitterService } from 'services/quitter'
+import { ToasterConfig } from 'angular2-toaster'
+
+import { SettingsModalComponent } from 'components/settingsModal'
+
+import 'angular2-toaster/lib/toaster.css'
+import 'global.less'
+
+const hterm = require('hterm-commonjs')
+var pty = require('pty.js');
+
+
+@Component({
+    selector: 'app',
+    template: require('./app.pug'),
+    styles: [require('./app.less')],
+})
+export class AppComponent {
+    constructor(
+        private hostApp: HostAppService,
+        private modal: ModalService,
+        private electron: ElectronService,
+        element: ElementRef,
+        log: LogService,
+        _quitter: QuitterService,
+    ) {
+        console.timeStamp('AppComponent ctor')
+
+        let logger = log.create('main')
+        logger.info('ELEMENTS client', electron.app.getVersion())
+
+        this.toasterConfig = new ToasterConfig({
+            mouseoverTimerStop: true,
+            preventDuplicates: true,
+            timeout: 4000,
+        })
+    }
+
+    toasterConfig: ToasterConfig
+
+    ngOnInit () {
+        let io
+                hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
+                let t = new hterm.hterm.Terminal()
+                t.onTerminalReady = function() {
+                t.installKeyboard()
+                  io = t.io.push();
+                  //#t.decorate(element.nativeElement);
+
+                  var cmd = pty.spawn('bash', [], {
+                    name: 'xterm-color',
+                    cols: 80,
+                    rows: 30,
+                    cwd: process.env.HOME,
+                    env: process.env
+                  });
+                  cmd.on('data', function(data) {
+                    io.writeUTF8(data);
+                    });
+
+
+                    io.onVTKeystroke = function(str) {
+                        cmd.write(str)
+                    };
+                    io.sendString = function(str) {
+                        cmd.write(str)
+                    };
+                    io.onTerminalResize = function(columns, rows) {
+                        cmd.resize(columns, rows)
+                    };
+                };
+                console.log(document.querySelector('#term'))
+                t.decorate(document.querySelector('#term'));
+    }
+
+    ngOnDestroy () {
+    }
+
+    showSettings() {
+        this.modal.open(SettingsModalComponent)
+    }
+}

+ 53 - 0
app/src/components/checkbox.less

@@ -0,0 +1,53 @@
+:host {
+    cursor: pointer;
+
+    &:focus {
+        background: rgba(255,255,255,.05);
+        border-radius: 5px;
+    }
+
+    &:active {
+        background: rgba(255,255,255,.1);
+        border-radius: 3px;
+    }
+
+    &[disabled] {
+        opacity: 0.5;
+    }
+
+    display: inline-flex;
+    flex-direction: row;
+    align-items: center;
+
+    .icon {
+        position: relative;
+        flex: none;
+        width: 14px;
+        height: 14px;
+
+        i {
+            position: absolute;
+            left: 0;
+            top: 1px;
+            transition: 0.25s opacity;
+            display: block;
+            font-size: 14px;
+        }
+
+        i.on {
+            color: yellow;
+        }
+
+        i.on, &.active i.off {
+            opacity: 0;
+        }
+
+        i.off, &.active i.on {
+            opacity: 1;
+        }
+    }
+
+    .name {
+        flex: auto;
+    }
+}

+ 4 - 0
app/src/components/checkbox.pug

@@ -0,0 +1,4 @@
+.icon((click)='click()', tabindex='0', [class.active]='model', (keyup.space)='click()')
+    i.fa.fa-square-o.off
+    i.fa.fa-check-square.on
+.text((click)='click()') {{text}}

+ 25 - 0
app/src/components/checkbox.ts

@@ -0,0 +1,25 @@
+import { NgZone, Component, Input, Output, EventEmitter } from '@angular/core'
+
+
+@Component({
+  selector: 'checkbox',
+  template: require('./checkbox.pug'),
+  styles: [require('./checkbox.less')]
+})
+export class CheckboxComponent {
+    public click() {
+        NgZone.assertInAngularZone()
+        if (this.disabled) {
+            return
+        }
+
+        this.model = !this.model
+        this.modelChange.emit(this.model)
+    }
+
+    @Input() model: boolean
+    @Output() modelChange = new EventEmitter()
+    @Input() disabled: boolean
+
+    @Input() text: string
+}

+ 69 - 0
app/src/components/settingsModal.less

@@ -0,0 +1,69 @@
+:host {
+    >.modal-body {
+        padding: 0 0 20px !important;
+    }
+
+    .form-group {
+        margin-left: 15px;
+    }
+
+    .version-info {
+        text-align: center;
+        font-size: 12px;
+        color: #aaa;
+        cursor: pointer;
+        padding: 10px 0;
+        -webkit-user-select: text;
+        transition: .25s all;
+
+        &:hover {
+            color: white;
+        }
+    }
+
+    .status-line {
+        display: flex;
+        padding: 5px 10px;
+
+        &.clickable {
+            &:hover {
+                background: rgba(0,0,0,.5);
+                cursor: pointer;
+            }
+        }
+
+        .icon {
+            flex: none;
+            padding: 7px 10px 0 0px;
+
+            img {
+                width: 32px;
+                height: 32px;
+                border-radius: 16px;
+                box-shadow: 0 1px 1px rgba(0, 0, 0, 0.5);
+            }
+
+            i {
+                width: 32px;
+                text-align: center;
+            }
+        }
+
+        .main {
+            flex: auto;
+            flex-direction: column;
+            display: flex;
+
+            .title {
+                flex: none;
+                font-size: 12px;
+            }
+
+            .value {
+                flex: auto;
+                font-size: 16px;
+                color: #ddd;
+            }
+        }
+    }
+}

+ 112 - 0
app/src/components/settingsModal.pug

@@ -0,0 +1,112 @@
+div.modal-body
+    ngb-tabset(type='tabs nav-justified')
+        ngb-tab
+            template(ngbTabTitle)
+                i.fa.fa-cog
+                | General
+            template(ngbTabContent)
+                .status-line.clickable(*ngIf='connectionHost', (click)='openWeb()')
+                    .icon
+                        i.fa.fa-rss.fa-2x.fa-live
+                    .main
+                        .title Server
+                        .value {{connectionHost}}
+
+                .status-line(*ngIf='!connectionHost')
+                    .icon
+                        i.fa.fa-rss.fa-2x
+                    .main
+                        .title Server
+                        .value Not connected
+
+                .status-line(*ngIf='!userInfo?.user')
+                    .icon
+                        img(src=require("img/user.png"))
+                    .main
+                        .title Login
+                        .value Not logged in
+
+                .status-line(*ngIf='userInfo?.user')
+                    .icon
+                        img([src]='userInfo.user.avatar || "../../assets/img/user.png"')
+                    .main
+                        .title Login
+                        .value {{ userInfo.user.full_name || userInfo.user.username }}
+
+                br
+
+                div.form-group
+                    checkbox(text='Remember connected workspaces', '[(model)]'='config.store.rememberWorkspaces')
+        ngb-tab
+            template(ngbTabTitle)
+                i.fa.fa-wrench
+                | Advanced
+            template(ngbTabContent)
+                div.form-group(*ngIf='isWindows || isLinux')
+                    div.input-group
+                        input.form-control(type='text', placeholder='SNFS projects folder', '[(ngModel)]'='config.store.snfsPath')
+                        div.input-group-btn
+                            button.btn.btn-default((click)='selectSNFSPath()')
+                                i.fa.fa-folder-open
+                div.form-group(*ngIf='isWindows')
+                    label First drive letter to use
+                    select.form-control('[(ngModel)]'='config.store.firstDrive')
+                        option(*ngFor='let x of drives', value='{{x}}') {{x}}:
+                div.form-group(*ngIf='isMac')
+                    label Extra NFS options
+                    input.form-control(type='text', '[(ngModel)]'='config.store.extraNFSOptions')
+                div.form-group(*ngIf='isMac')
+                    label Extra AFP options
+                    input.form-control(type='text', '[(ngModel)]'='config.store.extraAFPOptions')
+                div.form-group(*ngIf='isMac')
+                    label Extra SMB options
+                    input.form-control(type='text', '[(ngModel)]'='config.store.extraSMBOptions')
+                div.form-group(*ngIf='isLinux')
+                    label Extra NFS options
+                    input.form-control(type='text', '[(ngModel)]'='config.store.extraLinuxNFSOptions')
+                div.form-group(*ngIf='isLinux')
+                    label Extra SMB options
+                    input.form-control(type='text', '[(ngModel)]'='config.store.extraLinuxSMBOptions')
+
+        ngb-tab(*ngIf="apiServer.authorizedKeysStore.length > 0")
+            template(ngbTabTitle)
+                i.fa.fa-plug
+                | Apps
+            template(ngbTabContent)
+                .list-group
+                    .list-group-item(*ngFor="let key of apiServer.authorizedKeysStore")
+                        button.btn.btn-default((click)='apiServer.deauthorizeKey(key)')
+                            i.fa.fa-times
+                            span Disconnect this app
+                        div {{key.name}}
+
+        ngb-tab
+            template(ngbTabTitle)
+                i.fa.fa-info-circle
+                | About
+            template(ngbTabContent)
+                .form-group
+                    h1 ELEMENTS Client
+                    div syslink GmbH © {{year}}
+
+                .form-group
+                    label Version
+                    div {{version}}
+
+                .form-group
+                    button.btn.btn-default((click)='copyDiagnostics()') Copy diagnostic info
+
+div.modal-footer
+    div.btn-group.btn-group-justified
+        a.btn.btn-default((click)='logout()', *ngIf='elementsClient.userInfo')
+            i.fa.fa-fw.fa-arrow-left
+            br
+            | Log out
+        a.btn.btn-default((click)='quit()')
+            i.fa.fa-fw.fa-power-off
+            br
+            | Quit
+        a.btn.btn-default((click)='close()')
+            i.fa.fa-fw.fa-check
+            br
+            | Done

+ 44 - 0
app/src/components/settingsModal.ts

@@ -0,0 +1,44 @@
+import { Component } from '@angular/core'
+import { ElectronService } from 'services/electron'
+import { HostAppService, PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MAC } from 'services/hostApp'
+import { ConfigService } from 'services/config'
+import { QuitterService } from 'services/quitter'
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
+
+import * as os from 'os'
+
+
+@Component({
+  selector: 'settings-modal',
+  template: require('./settingsModal.pug'),
+  styles: [require('./settingsModal.less')],
+})
+export class SettingsModalComponent {
+    constructor(
+        private modalInstance: NgbActiveModal,
+        private hostApp: HostAppService,
+        private electron: ElectronService,
+        private quitter: QuitterService,
+        public config: ConfigService,
+    ) {
+        this.isWindows = hostApp.platform == PLATFORM_WINDOWS
+        this.isMac = hostApp.platform == PLATFORM_MAC
+        this.isLinux = hostApp.platform == PLATFORM_LINUX
+        this.version = electron.app.getVersion()
+        this.year = new Date().getFullYear()
+    }
+
+    isWindows: boolean
+    isMac: boolean
+    isLinux: boolean
+    year: number
+    version: string
+
+    ngOnDestroy() {
+        this.config.save()
+    }
+
+    close() {
+        this.modalInstance.close()
+    }
+}

+ 6 - 0
app/src/entry.preload.ts

@@ -0,0 +1,6 @@
+import 'source-sans-pro'
+
+import 'font-awesome/css/font-awesome.css'
+
+import '../assets/toaster-custom.less'
+import '../assets/bootstrap/bootstrap.less'

+ 25 - 0
app/src/entry.ts

@@ -0,0 +1,25 @@
+console.timeStamp('entry point')
+
+import 'core-js'
+import 'zone.js/dist/zone.js'
+import 'core-js/es7/reflect'
+import 'jquery'
+
+// Always land on the start view
+location.hash = ''
+
+import { AppModule } from 'app.module'
+import { enableProdMode } from '@angular/core'
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
+
+if (nodeRequire('electron-is-dev')) {
+    console.warn('Running in debug mode')
+} else {
+    enableProdMode()
+}
+
+console.timeStamp('angular bootstrap started')
+platformBrowserDynamic().bootstrapModule(AppModule)
+
+
+process.emitWarning = function () { console.log(arguments) }

+ 176 - 0
app/src/global.less

@@ -0,0 +1,176 @@
+@import "~bootstrap/include.less";
+
+
+html.platform-win32 {
+    body.focused {
+        //border: 1px solid #9c9c00 !important;
+    }
+}
+
+body {
+    border: 1px solid #131313;
+    transition: 0.5s border;
+    overflow: hidden;
+    min-height: 100vh;
+}
+
+.no-drag, a, button, checkbox, .form-control, #toast-container {
+    -webkit-app-region: no-drag;
+    outline: 0 !important;
+
+    * {
+        outline: 0 !important;
+        -webkit-app-region: no-drag;
+    }
+}
+
+.form-control {
+    -webkit-user-select: initial;
+}
+
+.window-resizer {
+    -webkit-app-region: no-drag;
+    position: fixed;
+    width: 10px;
+    height: 10px;
+}
+
+.window-resizer-tl {
+    left: 0;
+    top: 0;
+}
+
+
+
+.no-wrap {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.word-wrap {
+    word-wrap: break-word;
+    word-break: break-all;
+}
+
+
+#toast-container.toast-top-full-width {
+    width: 100%;
+    top: 50px;
+
+    > div {
+        width: 100%;
+        border-radius: 0;
+        box-shadow: 0 0 2px rgba(0,0,0,.75);
+        opacity: 1;
+        filter: none;
+    }
+}
+
+.avatar {
+    margin: 20px;
+    width: 100px;
+    height: 100px;
+    border-radius: 50px;
+    box-shadow: 0 1px 1px rgba(0,0,0,.5);
+}
+
+
+
+.fa-live {
+    color: #7aff00;
+
+    .list-group-item &.fa-2x {
+        top: 10px;
+        position: relative;
+    }
+}
+
+
+.otp-input {
+    height: 50px;
+
+    input {
+        height: 50px;
+        font-size: 41px;
+        font-family: monospace;
+        text-align: center;
+    }
+
+    .btn {
+        height: 50px;
+        width: 50px;
+    }
+}
+
+
+ngb-modal-backdrop {
+    // ngbmodalwindow has its own, properly animated backdrop
+    background: transparent !important;
+}
+
+ngb-modal-window.fade.in {
+    &.out {
+        opacity: 0;
+
+        .modal-dialog {
+            transform: translate(0, -25%);
+        }
+    }
+}
+
+ngb-tabset {
+    >ul.nav-tabs.nav-justified {
+        border-bottom: none;
+        margin-bottom: 10px;
+        background: rgba(0,0,0,.25);
+
+        .nav-item .nav-link {
+            background: transparent;
+            border: none;
+
+            &.active {
+                background: rgba(0,0,0,.5);
+                border-bottom: 1px solid #777;
+            }
+
+            i {
+                display: block;
+                text-align: center;
+                font-size: 18px;
+                margin: 0 0 5px;
+            }
+        }
+    }
+
+    >.tab-content {
+        padding: 10px;
+    }
+}
+
+
+.btn {
+    i + * {
+        margin-left: 5px;
+    }
+}
+
+.list-group-item {
+    margin: none;
+
+    > .btn {
+        float: right;
+        margin: -7px -11px 0 0;
+        background: transparent;
+        box-shadow: none;
+
+        &:hover {
+            background: rgba(0,0,0,.25);
+        }
+    }
+}
+
+.ps-container.ps-in-scrolling>.ps-scrollbar-y-rail,
+.ps-container:hover>.ps-scrollbar-y-rail:hover {
+    background: rgba(0,0,0,.5) !important;
+}

+ 88 - 0
app/src/services/config.ts

@@ -0,0 +1,88 @@
+import { Injectable } from '@angular/core'
+import { HostAppService, PLATFORM_MAC, PLATFORM_WINDOWS } from 'services/hostApp'
+const Config = nodeRequire('electron-config')
+const exec = nodeRequire('child-process-promise').exec
+import * as fs from 'fs'
+
+
+@Injectable()
+export class ConfigService {
+    constructor(
+        private hostApp: HostAppService,
+    ) {
+        this.config = new Config({name: 'config'})
+        this.load()
+    }
+
+    private config: any
+    private store: any
+
+    migrate() {
+        if (!this.has('migrated')) {
+            if (this.hostApp.platform == PLATFORM_WINDOWS) {
+                let configPath = `${this.hostApp.getPath('documents')}\\.elements.conf`
+                let config = null
+                try {
+                    config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
+                    console.log('Migrating configuration:', config)
+                    this.set('host', config.Hostname)
+                    this.set('username', config.Username)
+                    this.set('firstDrive', config.FirstDrive)
+                } catch (err) {
+                    console.error('Could not migrate the config:', err)
+                }
+                this.set('migrated', 1)
+                this.save()
+                return Promise.resolve()
+            }
+            if (this.hostApp.platform == PLATFORM_MAC) {
+                return Promise.all([
+                    exec('defaults read ~/Library/Preferences/com.syslink.Elements.plist connection_host').then((result) => {
+                        this.set('host', result.stdout.trim())
+                    }),
+                    exec('defaults read ~/Library/Preferences/com.syslink.Elements.plist connection_username').then((result) => {
+                        this.set('username', result.stdout.trim())
+                    }),
+                ]).then(() => {
+                    this.set('migrated', 1)
+                    this.save()
+                }).catch((err) => {
+                    console.error('Could not migrate the config:', err)
+                    this.set('migrated', 1)
+                    this.save()
+                })
+            }
+        }
+        return Promise.resolve()
+    }
+
+    set(key: string, value: any) {
+        this.save()
+        this.config.set(key, value)
+        this.load()
+    }
+
+    get(key: string): any {
+        this.save()
+        return this.config.get(key)
+    }
+
+    has(key: string): boolean {
+        this.save()
+        return this.config.has(key)
+    }
+
+    delete(key: string) {
+        this.save()
+        this.config.delete(key)
+        this.load()
+    }
+
+    load() {
+        this.store = this.config.store
+    }
+
+    save() {
+        this.config.store = this.store
+    }
+}

+ 39 - 0
app/src/services/electron.ts

@@ -0,0 +1,39 @@
+import { Injectable } from '@angular/core'
+
+
+@Injectable()
+export class ElectronService {
+    constructor() {
+        if (process.env.TEST_ENV) {
+            this.initTest()
+        } else {
+            this.init()
+        }
+    }
+
+    init() {
+        this.electron = require('electron')
+        this.remoteElectron = this.remoteRequire('electron')
+        this.app = this.remoteElectron.app
+        this.dialog = this.remoteElectron.dialog
+        this.shell = this.electron.shell
+        this.clipboard = this.electron.clipboard
+        this.ipcRenderer = this.electron.ipcRenderer
+    }
+
+    initTest() {
+        ;
+    }
+
+    remoteRequire(name: string): any {
+        return this.electron.remote.require(name)
+    }
+
+    app: any
+    ipcRenderer: any
+    shell: any
+    dialog: any
+    clipboard: any
+    private electron: any
+    private remoteElectron: any
+}

+ 69 - 0
app/src/services/hostApp.ts

@@ -0,0 +1,69 @@
+import { Injectable, NgZone, EventEmitter } from '@angular/core'
+import { ElectronService } from 'services/electron'
+import { Logger, LogService } from 'services/log'
+
+export const PLATFORM_WINDOWS = 'win32'
+export const PLATFORM_MAC = 'darwin'
+export const PLATFORM_LINUX = 'linux'
+
+
+@Injectable()
+export class HostAppService {
+    constructor(
+        private zone: NgZone,
+        private electron: ElectronService,
+        log: LogService,
+    ) {
+        this.platform = require('os').platform()
+        this.logger = log.create('hostApp')
+
+        electron.ipcRenderer.on('host:quit-request', () => this.zone.run(() => this.quitRequested.emit()))
+
+        electron.ipcRenderer.on('uncaughtException', function(err) {
+            console.error('Unhandled exception:', err)
+        })
+
+        this.ready.subscribe(() => {
+            electron.ipcRenderer.send('app:ready')
+        })
+    }
+
+    platform: string;
+    quitRequested = new EventEmitter<any>()
+    ready = new EventEmitter<any>()
+
+    private logger: Logger;
+
+    getWindow() {
+        return this.electron.app.window
+    }
+
+    getShell() {
+        return this.electron.shell
+    }
+
+    getAppPath() {
+        return this.electron.app.getAppPath()
+    }
+
+    getPath(type: string) {
+        return this.electron.app.getPath(type)
+    }
+
+    openDevTools() {
+        this.electron.app.webContents.openDevTools()
+    }
+
+    setWindowCloseable(flag: boolean) {
+        this.electron.ipcRenderer.send('window-closeable', flag)
+    }
+
+    focusWindow() {
+        this.electron.ipcRenderer.send('window-focus')
+    }
+
+    quit() {
+        this.logger.info('Quitting')
+        this.electron.app.quit()
+    }
+}

+ 25 - 0
app/src/services/log.ts

@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core'
+
+
+export class Logger {
+    constructor(
+        private name: string,
+    ) {}
+
+    log(level: string, ...args: any[]) {
+        args.splice(0, 0, this.name + ':')
+        console[level](...args)
+    }
+
+    debug(...args: any[]) { this.log('debug', ...args) }
+    info(...args: any[]) { this.log('info', ...args) }
+    warn(...args: any[]) { this.log('warn', ...args) }
+    error(...args: any[]) { this.log('error', ...args) }
+}
+
+@Injectable()
+export class LogService {
+    create (name: string): Logger {
+        return new Logger(name)
+    }
+}

+ 29 - 0
app/src/services/modal.ts

@@ -0,0 +1,29 @@
+import { Injectable, NgZone } from '@angular/core';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+
+
+@Injectable()
+export class ModalService {
+    constructor(
+        private zone: NgZone,
+        private ngbModal: NgbModal,
+    ) {}
+
+    open(content: any, config?: any) {
+        config = config || {}
+        config.windowClass = 'out'
+        let modal = this.ngbModal.open(content, config)
+
+        let fx = (<any>modal)._removeModalElements.bind(modal);
+
+        (<any>modal)._removeModalElements = () => {
+            (<any>modal)._windowCmptRef.instance.windowClass = 'out'
+            setTimeout(() => fx(), 500)
+        }
+        setTimeout(() => {
+            (<any>modal)._windowCmptRef.instance.windowClass = ''
+        }, 1)
+
+        return modal
+    }
+}

+ 48 - 0
app/src/services/notify.ts

@@ -0,0 +1,48 @@
+import { Injectable } from '@angular/core'
+import { ToasterService } from 'angular2-toaster'
+import { LogService } from 'services/log'
+
+
+@Injectable()
+export class NotifyService {
+    constructor(
+        private toaster: ToasterService,
+        private log: LogService,
+    ) {}
+
+    pop(options) {
+        this.toaster.pop(options)
+    }
+
+    info(title: string, body: string = null) {
+        return this.pop({
+            type: 'info',
+            title, body,
+            timeout: 4000,
+        })
+    }
+
+    success(title: string, body: string = null) {
+        return this.pop({
+            type: 'success',
+            title, body,
+            timeout: 4000,
+        })
+    }
+
+    warning(title: string, body: string = null) {
+        return this.pop({
+            type: 'warning',
+            title, body,
+            timeout: 4000,
+        })
+    }
+
+    error(title: string, body: string = null) {
+        return this.pop({
+            type: 'error',
+            title, body,
+            timeout: 4000,
+        })
+    }
+}

+ 21 - 0
app/src/services/quitter.ts

@@ -0,0 +1,21 @@
+import { Injectable } from '@angular/core'
+import { HostAppService } from 'services/hostApp'
+import { ElectronService } from 'services/electron'
+
+
+@Injectable()
+export class QuitterService {
+    constructor(
+        private electron: ElectronService,
+        private hostApp: HostAppService,
+    ) {
+        hostApp.quitRequested.subscribe(() => {
+            this.quit()
+        })
+    }
+
+    quit() {
+        this.hostApp.setWindowCloseable(true)
+        this.hostApp.quit()
+    }
+}

+ 62 - 0
build/config.gypi

@@ -0,0 +1,62 @@
+# Do not edit. File was generated by node-gyp's "configure" step
+{
+  "target_defaults": {
+    "cflags": [],
+    "default_configuration": "Release",
+    "defines": [],
+    "include_dirs": [],
+    "libraries": []
+  },
+  "variables": {
+    "asan": 0,
+    "debug_devtools": "node",
+    "force_dynamic_crt": 0,
+    "host_arch": "x64",
+    "icu_data_file": "icudt57l.dat",
+    "icu_data_in": "..\\..\\deps/icu-small\\source/data/in\\icudt57l.dat",
+    "icu_endianness": "l",
+    "icu_gyp_path": "tools/icu/icu-generic.gyp",
+    "icu_locales": "en,root",
+    "icu_path": "deps/icu-small",
+    "icu_small": "true",
+    "icu_ver_major": "57",
+    "node_byteorder": "little",
+    "node_enable_d8": "false",
+    "node_enable_v8_vtunejit": "false",
+    "node_install_npm": "true",
+    "node_module_version": 48,
+    "node_no_browser_globals": "false",
+    "node_prefix": "/usr/local",
+    "node_release_urlbase": "https://nodejs.org/download/release/",
+    "node_shared": "false",
+    "node_shared_cares": "false",
+    "node_shared_http_parser": "false",
+    "node_shared_libuv": "false",
+    "node_shared_openssl": "false",
+    "node_shared_zlib": "false",
+    "node_tag": "",
+    "node_use_bundled_v8": "true",
+    "node_use_dtrace": "false",
+    "node_use_etw": "true",
+    "node_use_lttng": "false",
+    "node_use_openssl": "true",
+    "node_use_perfctr": "true",
+    "node_use_v8_platform": "true",
+    "openssl_fips": "",
+    "openssl_no_asm": 0,
+    "shlib_suffix": "so.48",
+    "target_arch": "x64",
+    "v8_enable_gdbjit": 0,
+    "v8_enable_i18n_support": 1,
+    "v8_inspector": "true",
+    "v8_no_strict_aliasing": 1,
+    "v8_optimized_debug": 0,
+    "v8_random_seed": 0,
+    "v8_use_snapshot": "true",
+    "want_separate_host_toolset": 0,
+    "nodedir": "C:\\cygwin64\\home\\Avid\\.node-gyp\\iojs-1.3.5",
+    "copy_dev_lib": "true",
+    "standalone_static_library": 1,
+    "target": "1.3.5"
+  }
+}

BIN
build/icon.ico


BIN
build/icons/1024x1024.png


BIN
build/icons/128x128.png


BIN
build/icons/16x16.png


BIN
build/icons/256x256.png


BIN
build/icons/32x32.png


BIN
build/icons/512x512.png


BIN
build/icons/64x64.png


BIN
build/logo.png


+ 35 - 0
build/mac/Distribution.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<installer-gui-script minSpecVersion="1">
+    <title>ELEMENTS Client</title>
+    <options customize="never" require-scripts="true" rootVolumeOnly="true" />
+
+    <choices-outline>
+        <line choice="default">
+            <line choice="com.syslink.elements.driver" />
+            <line choice="com.syslink.Elements" />
+            <line choice="com.syslink.afptuner" />
+        </line>
+    </choices-outline>
+
+    <choice id="default"/>
+
+    <choice id="com.syslink.elements.driver">
+        <pkg-ref id="com.syslink.elements.driver"/>
+    </choice>
+    <pkg-ref id="com.syslink.elements.driver" version="1" auth="root">ElementsDriver.pkg</pkg-ref>
+
+    <choice id="com.syslink.Elements">
+        <pkg-ref id="com.syslink.Elements"/>
+    </choice>
+    <pkg-ref id="com.syslink.Elements" version="1" auth="root">Elements.pkg</pkg-ref>
+    <pkg-ref id="com.syslink.Elements">
+        <must-close>
+            <app id="com.syslink.Elements"/>
+        </must-close>
+    </pkg-ref>
+
+    <choice id="com.syslink.afptuner" title="AvidSharedStorageAccess">
+        <pkg-ref id="com.syslink.afptuner"/>
+    </choice>
+    <pkg-ref id="com.syslink.afptuner" version="1" auth="root">AFPTuner.pkg</pkg-ref>
+</installer-gui-script>

+ 20 - 0
build/mac/Elements.component.plist

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+	<dict>
+		<key>BundleHasStrictIdentifier</key>
+		<true/>
+		<key>BundleIsRelocatable</key>
+		<false/>
+		<key>BundleIsVersionChecked</key>
+		<false/>
+		<key>BundleOverwriteAction</key>
+		<string>upgrade</string>
+		<key>BundlePostInstallScriptPath</key>
+		<string>Elements.postinst.sh</string>
+		<key>RootRelativeBundlePath</key>
+		<string>Applications/ELEMENTS.app</string>
+	</dict>
+</array>
+</plist>

+ 44 - 0
build/mac/Elements.postinst.sh

@@ -0,0 +1,44 @@
+#!/bin/bash
+set -e
+
+cat > /Library/LaunchDaemons/com.elements.VolumesFix.plist << EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+<key>KeepAlive</key>
+<false/>
+<key>Label</key>
+<string>com.elements.VolumesFix</string>
+<key>ProgramArguments</key>
+<array>
+<string>/bin/bash</string>
+<string>-c</string>
+<string>sleep 3; chmod 777 /Volumes</string>
+</array>
+<key>RunAtLoad</key>
+<true/>
+<key>StandardErrorPath</key>
+<string>/dev/null</string>
+<key>StandardOutPath</key>
+<string>/dev/null</string>
+<key>UserName</key>
+<string>root</string>
+</dict>
+</plist>
+EOF
+
+chmod 600 /Library/LaunchDaemons/com.elements.VolumesFix.plist
+
+cat > /etc/nsmb.conf << EOF
+[default]
+minauth=none
+streams=yes
+soft=yes
+notify_off=yes
+port445=no_netbios
+signing_required=false
+EOF
+
+launchctl load -w /Library/LaunchDaemons/com.elements.VolumesFix.plist
+launchctl start com.elements.VolumesFix

+ 50 - 0
build/mac/Info.plist

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en_US</string>
+	<key>CFBundleExecutable</key>
+	<string>ELEMENTS</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.syslink.elements</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION}</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleURLName</key>
+			<string>Elements Client</string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>elements-client</string>
+			</array>
+		</dict>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>619</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>10.8.0</string>
+	<key>LSUIElement</key>
+	<false/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+	</dict>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2016 Syslink GmbH. All rights reserved.</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>AtomApplication</string>
+</dict>
+</plist>

BIN
build/mac/codesign


BIN
build/mac/icon.icns


+ 34 - 0
build/windows/build.wxs

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix RequiredVersion="3.6.2830.0" xmlns="http://schemas.microsoft.com/wix/2006/wi"
+     xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
+     <Bundle Name="ELEMENTS" Version="$(var.Version)" Manufacturer="ELEMENTS.tv" UpgradeCode="508475fc-0e76-4cd1-8e98-6953023ba518"
+         HelpUrl="http://elements.tv"
+         Copyright="Copyright © 2016 ELEMENTS" IconSourceFile="build/icon.ico"
+         AboutUrl="http://elements.tv">
+
+         <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
+             <bal:WixStandardBootstrapperApplication
+                 LicenseUrl=""
+                 LogoFile="build\logo.png"
+                 ThemeFile="build\windows\wix-theme.xml"
+             />
+         </BootstrapperApplicationRef>
+
+         <Chain>
+             <MsiPackage
+                 Id="ClientMSI"
+                 Compressed="yes"
+                 ForcePerMachine="yes"
+                 SourceFile="dist\elements-app.msi"
+                 Vital="yes">
+             </MsiPackage>
+             <MsiPackage
+                 Id="DriverMSI"
+                 Compressed="yes"
+                 ForcePerMachine="yes"
+                 SourceFile="build/windows/ElementsDriver_x64.msi"
+                 Vital="yes">
+             </MsiPackage>
+         </Chain>
+     </Bundle>
+</Wix>

+ 84 - 0
build/windows/elements.wxs

@@ -0,0 +1,84 @@
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+    <Product Id="*" UpgradeCode="37484543-5276-2386-5427-275941245342"
+            Name="ELEMENTS" Version="$(var.ProductVersion)" Manufacturer="ELEMENTS.tv" Language="1033">
+        <Package InstallerVersion="200"
+            Compressed="yes"
+            Comments="Windows Installer Package"
+            Platform="x64"
+            InstallScope="perMachine"
+            InstallPrivileges="elevated" />
+        <MajorUpgrade AllowDowngrades="yes" Schedule="afterInstallValidate" />
+        <Media Id="1" Cabinet="product.cab" EmbedCab="yes"/>
+
+        <Feature Id="DefaultFeature" Level="1">
+            <ComponentRef Id="RegistryEntries" />
+            <ComponentRef Id="AppDir"/>
+            <ComponentRef Id="AvidSharedStorageAccess"/>
+            <ComponentRef Id="ApplicationShortcutDesktop"/>
+            <ComponentGroupRef Id="Files" />
+        </Feature>
+
+        <Directory Id="TARGETDIR" Name="SourceDir">
+            <Directory Id="DesktopFolder" Name="Desktop">
+                <Component Id="ApplicationShortcutDesktop" Guid="*">
+                    <Shortcut Id="ApplicationDesktopShortcut"
+                        Name="ELEMENTS"
+                        Description="ELEMENTS client app"
+                        Target="[INSTALLDIR]\\Elements.exe"
+                        WorkingDirectory="INSTALLDIR"/>
+                    <RemoveFolder Id="DesktopFolder" On="uninstall"/>
+                    <RegistryValue
+                        Root="HKCU"
+                        Key="Software\ELEMENTS"
+                        Name="installed"
+                        Type="integer"
+                        Value="1"
+                        KeyPath="yes"/>
+                </Component>
+            </Directory>
+            <Directory Id="ProgramFiles64Folder">
+                <Directory Id="ElementsDir" Name="ELEMENTS">
+                    <Directory Id="INSTALLDIR" Name="ELEMENTS Client">
+                        <Component Id="AppDir" Guid="284957a6-a462-4e34-babd-c17800f11054" Win64="yes">
+                            <CreateFolder />
+                            <!--RemoveFile Id="RemoveFilesFromAppDirectory" Name="*.*" On="uninstall" /-->
+                            <!--RemoveFolder Id="AppDir" On="uninstall"/-->
+                        </Component>
+                    </Directory>
+                </Directory>
+            </Directory>
+            <Component Id="RegistryEntries" Guid="572998d8-719e-4124-8fe6-6d4f8b855d7b">
+                <RegistryKey Root="HKLM"
+                     Key="system\currentcontrolset\services\AvidFs"
+                     Action="create">
+                     <RegistryValue Type="string" Name="Description" Value="AIFMRX" />
+                     <RegistryValue Type="string" Name="DisplayName" Value="AIFMRX" />
+                     <RegistryValue Type="integer" Name="ErrorControl" Value="1" />
+                     <RegistryValue Type="string" Name="Group" Value="Network" />
+                     <RegistryValue Type="string" Name="ImagePath" Value="System32\DRIVERS\aifmrx.sys" />
+                     <RegistryValue Type="integer" Name="Start" Value="1" />
+                     <RegistryValue Type="integer" Name="Type" Value="2" />
+                 </RegistryKey>
+                 <RegistryKey Root="HKLM"
+                      Key="system\currentcontrolset\services\AifMRx\NetworkProvider"
+                      Action="create">
+                      <RegistryValue Type="string" Name="DeviceName" Value="\Device\AvidFs" />
+                      <RegistryValue Type="string" Name="Name" Value="Interface Network" />
+                      <RegistryValue Type="string" Name="ProviderPath" Value="System32\aifmrxnp.dll" />
+                  </RegistryKey>
+                  <RegistryKey Root="HKLM"
+                       Key="system\CurrentControlSet\services\LanmanWorkstation\Parameters"
+                       Action="create">
+                       <RegistryValue Type="integer" Name="DisableLargeMtu" Value="0" KeyPath="yes" />
+                       <RegistryValue Type="integer" Name="DisableBandwidthThrottling" Value="1" />
+                       <RegistryValue Type="integer" Name="EnableWsd" Value="0" />
+                  </RegistryKey>
+            </Component>
+            <Directory Id="System64Folder" Name="SystemFolder">
+                <Component Id="AvidSharedStorageAccess" Guid="972c67f2-ee17-4b20-8939-b92cfa13fcf6" NeverOverwrite="yes" Win64="yes" Permanent="yes">
+                    <File Id="AvidSharedStorageAccess.dll" Source="build\windows\AvidSharedStorageAccess.dll" KeyPath="yes"/>
+                </Component>
+            </Directory>
+        </Directory>
+    </Product>
+</Wix>

BIN
build/windows/signtool.exe


+ 80 - 0
build/windows/wix-theme.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
+
+
+<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
+    <Window Width="300" Height="360" HexStyle="100a0000" FontId="0">#(loc.Caption)</Window>
+    <Font Id="0" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
+    <Font Id="1" Height="-24" Weight="500" Foreground="000000">Segoe UI</Font>
+    <Font Id="2" Height="-22" Weight="500" Foreground="666666">Segoe UI</Font>
+    <Font Id="3" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
+    <Font Id="4" Height="-12" Weight="500" Foreground="ff0000" Background="FFFFFF" Underline="yes">Segoe UI</Font>
+
+    <Image X="30" Y="30" Width="256" Height="60" ImageFile="logo.png" Visible="yes"/>
+
+    <Page Name="Help">
+        <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.HelpHeader)</Text>
+        <Text X="11" Y="112" Width="-11" Height="-35" FontId="3" DisablePrefix="yes">#(loc.HelpText)</Text>
+        <Button Name="HelpCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.HelpCloseButton)</Button>
+    </Page>
+    <Page Name="Install">
+        <!--Hypertext Name="EulaHyperlink" X="11" Y="121" Width="-11" Height="51" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallLicenseLinkText)</Hypertext>
+        <Checkbox Name="EulaAcceptCheckbox" X="-11" Y="-41" Width="260" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallAcceptCheckbox)</Checkbox-->
+        <Button Name="InstallButton" X="90" Y="-120" Width="120" Height="50" TabStop="yes" FontId="0">#(loc.InstallInstallButton)</Button>
+        <Button Name="WelcomeCancelButton" X="110" Y="-80" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
+    </Page>
+    <Page Name="Options">
+        <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.OptionsHeader)</Text>
+        <Text X="11" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OptionsLocationLabel)</Text>
+        <Editbox Name="FolderEditbox" X="11" Y="143" Width="-91" Height="21" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
+        <Button Name="BrowseButton" X="-11" Y="142" Width="75" Height="23" TabStop="yes" FontId="3">#(loc.OptionsBrowseButton)</Button>
+        <Button Name="OptionsOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsOkButton)</Button>
+        <Button Name="OptionsCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsCancelButton)</Button>
+    </Page>
+    <Page Name="FilesInUse">
+      <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.FilesInUseHeader)</Text>
+      <Text X="11" Y="121" Width="-11" Height="34" FontId="3" DisablePrefix="yes">#(loc.FilesInUseLabel)</Text>
+      <Text Name="FilesInUseText"  X="11" Y="150" Width="-11" Height="-86" FontId="3" DisablePrefix="yes" HexStyle="0x0000C000"></Text>
+
+      <Button Name="FilesInUseCloseRadioButton" X="11" Y="-60" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseCloseRadioButton)</Button>
+      <Button Name="FilesInUseDontCloseRadioButton" X="11" Y="-40" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseDontCloseRadioButton)</Button>
+
+      <Button Name="FilesInUseOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FilesInUseOkButton)</Button>
+      <Button Name="FilesInUseCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FilesInUseCancelButton)</Button>
+    </Page>
+    <Page Name="Progress">
+        <Text X="30" Y="120" Width="-30" Height="30" FontId="2" DisablePrefix="yes">#(loc.ProgressHeader)</Text>
+        <Text X="30" Y="150" Width="70" Height="17" FontId="3" DisablePrefix="yes">#(loc.ProgressLabel)</Text>
+        <Text Name="OverallProgressPackageText" X="30" Y="200" Width="-30" Height="17" FontId="3" DisablePrefix="yes">#(loc.OverallProgressPackageText)</Text>
+        <Progressbar Name="OverallCalculatedProgressbar" X="30" Y="220" Width="-30" Height="20" />
+        <Button Name="ProgressCancelButton" X="110" Y="-40" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.ProgressCancelButton)</Button>
+    </Page>
+    <Page Name="Modify">
+        <Text X="30" Y="110" Width="-30" Height="30" FontId="2" DisablePrefix="yes">#(loc.ModifyHeader)</Text>
+
+        <Button Name="UninstallButton" X="90" Y="-100" Width="120" Height="50" TabStop="yes" FontId="0">#(loc.ModifyUninstallButton)</Button>
+        <Button Name="RepairButton" X="110" Y="-60" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.ModifyRepairButton)</Button>
+        <Button Name="ModifyCancelButton" X="110" Y="-30" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.ModifyCloseButton)</Button>
+    </Page>
+    <Page Name="Success">
+        <Text Name="SuccessHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Success</Text>
+        <Text Name="SuccessInstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Installed successfully</Text>
+        <Text Name="SuccessRepairHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Repaired successfully</Text>
+        <Text Name="SuccessUninstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Uninstalled successfully</Text>
+
+        <Button Name="LaunchButton" X="90" Y="-100" Width="120" Height="50" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessLaunchButton)</Button>
+        <Button Name="SuccessCancelButton" X="110" Y="-60" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.SuccessCloseButton)</Button>
+    </Page>
+    <Page Name="Failure">
+        <Text Name="FailureHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureHeader)</Text>
+        <Text Name="FailureInstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Setup failed</Text>
+        <Text Name="FailureUninstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Uninstall failed</Text>
+        <Text Name="FailureRepairHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Repair failed</Text>
+        <Hypertext Name="FailureLogFileLink" X="30" Y="145" Width="-30" Height="50" FontId="3" TabStop="yes" HideWhenDisabled="yes">#(loc.FailureHyperlinkLogText)</Hypertext>
+        <Hypertext Name="FailureMessageText" X="30" Y="195" Width="-30" Height="50" FontId="3" TabStop="yes" HideWhenDisabled="yes" />
+        <Text Name="FailureRestartText" X="-30" Y="255" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRestartText)</Text>
+
+        <Button Name="FailureRestartButton" X="90" Y="-100" Width="120" Height="50" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FailureRestartButton)</Button>
+        <Button Name="FailureCloseButton" X="110" Y="-60" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.FailureCloseButton)</Button>
+    </Page>
+</Theme>

+ 76 - 0
package.json

@@ -0,0 +1,76 @@
+{
+  "name": "term",
+  "devDependencies": {
+    "apply-loader": "^0.1.0",
+    "awesome-typescript-loader": "2.2.4",
+    "css-loader": "0.26.1",
+    "electron": "^1.4.13",
+    "electron-builder": "10.6.1",
+    "electron-osx-sign": "electron-userland/electron-osx-sign#f092181a1bffa2b3248a23ee28447a47e14a8f04",
+    "electron-rebuild": "1.4.0",
+    "file-loader": "^0.9.0",
+    "font-awesome": "4.7.0",
+    "html-loader": "^0.4.4",
+    "less": "^2.7.1",
+    "less-loader": "^2.2.3",
+    "node-gyp": "^3.4.0",
+    "pug-html-loader": "^1.0.9",
+    "pug-loader": "^2.3.0",
+    "pug-static-loader": "0.0.1",
+    "raw-loader": "^0.5.1",
+    "style-loader": "^0.13.1",
+    "to-string-loader": "^1.1.5",
+    "tslint": "4.0.2",
+    "typescript": "2.1.1",
+    "typings": "2.0.0",
+    "url-loader": "^0.5.7",
+    "val-loader": "^0.5.0",
+    "webpack": "2.2.0-rc.0"
+  },
+  "build": {
+    "appId": "com.elements.benchmark",
+    "productName": "ELEMENTS Benchmark",
+    "compression": "normal",
+    "win": {
+      "target": "zip",
+      "icon": "./app/assets/img/shortcut.ico"
+    },
+    "mac": {
+      "category": "public.app-category.video",
+      "icon": "./build/mac/icon.icns",
+      "identity": "Syslink GmbH"
+    },
+    "linux": {
+      "category": "Network",
+      "target": "AppImage",
+      "icon": "./app/assets/img/icon.png"
+    }
+  },
+  "scripts": {
+    "pack": "build --dir",
+    "postinstall": "install-app-deps",
+    "dist": "build"
+  },
+  "dependencies": {
+    "@angular/common": "2.3.1",
+    "@angular/compiler": "2.3.1",
+    "@angular/core": "2.3.1",
+    "@angular/forms": "2.3.1",
+    "@angular/http": "2.3.1",
+    "@angular/platform-browser": "2.3.1",
+    "@angular/platform-browser-dynamic": "2.3.1",
+    "@angular/platform-server": "2.3.1",
+    "@angular/router": "3.3.1",
+    "@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.15",
+    "angular2-localstorage": "github:AilisObrian/angular2-localstorage",
+    "angular2-perfect-scrollbar": "^1.1.0",
+    "angular2-toaster": "^1.1.0",
+    "bootstrap": "^3.3.7",
+    "core-js": "^2.4.1",
+    "jquery": "^3.1.1",
+    "rxjs": "5.0.0-rc.4",
+    "source-sans-pro": "^2.0.10",
+    "hterm-commonjs": "^1.0.0",
+    "zone.js": "0.7.2"
+  }
+}

+ 26 - 0
tsconfig.json

@@ -0,0 +1,26 @@
+{
+    "compilerOptions": {
+        "baseUrl": "./app/src",
+        "module": "commonjs",
+        "target": "es5",
+        "declaration": false,
+        "noImplicitAny": false,
+        "removeComments": false,
+        "emitDecoratorMetadata": true,
+        "experimentalDecorators": true,
+        "sourceMap": true,
+        "noUnusedParameters": true,
+        "noImplicitReturns": true,
+        "noFallthroughCasesInSwitch": true
+    },
+    "compileOnSave": false,
+    "exclude": [
+        "node_modules"
+    ],
+    "files": [
+        "app/src/app.d.ts",
+        "app/src/entry.ts",
+        "typings/index.d.ts",
+        "node_modules/rxjs/Rx.d.ts"
+    ]
+}

+ 22 - 0
tslint.json

@@ -0,0 +1,22 @@
+{
+    "rules": {
+        "indent": [true, "spaces"],
+        "quotemark": [true, "single"],
+        "semicolon": false,
+        "no-inferrable-types": [true, "ignore-params"],
+        "curly": true,
+        "no-duplicate-key": true,
+        "no-duplicate-variable": true,
+        "no-empty": true,
+        "no-eval": true,
+        "no-invalid-this": true,
+        "no-shadowed-variable": true,
+        "no-unreachable": true,
+        "no-unused-expression": true,
+        "no-unused-new": true,
+        "no-unused-variable": true,
+        "no-use-before-declare": true,
+        "no-var-keyword": true,
+        "new-parens": true
+    }
+}

+ 8 - 0
typings.json

@@ -0,0 +1,8 @@
+{
+  "globalDependencies": {
+    "core-js": "registry:dt/core-js#0.0.0+20160914114559",
+    "electron": "registry:dt/electron#1.3.3+20161012142539",
+    "jquery": "registry:dt/jquery#1.10.0+20160929162922",
+    "node": "registry:dt/node#6.0.0+20161014191813"
+  }
+}

+ 98 - 0
webpack.config.js

@@ -0,0 +1,98 @@
+const path = require("path")
+const webpack = require("webpack")
+
+module.exports = {
+    target: 'node',
+    entry: {
+        'index.ignore': 'file-loader?name=index.html!val-loader!pug-html-loader!./app/index.pug',
+        'preload': './app/src/entry.preload.ts',
+        'bundle': './app/src/entry.ts',
+    },
+    devtool: 'source-map',
+    output: {
+        path: 'app/assets/webpack',
+        pathinfo: true,
+        //publicPath: 'assets/webpack/',
+        filename: '[name].js'
+    },
+    resolve: {
+        modules: ['app/src/', 'node_modules', 'app/assets/'],
+        extensions: ['.ts', '.js'],
+    },
+    module: {
+        loaders: [
+            {
+                test: /\.ts$/,
+                loader: 'awesome-typescript-loader'
+            },
+            {
+              test: /\.pug$/,
+              exclude: [
+                /index.pug/
+              ],
+              loaders: [
+                {
+                  loader: 'apply-loader'
+                },
+                {
+                  loader: 'pug-loader'
+                }
+              ]
+            },
+            { test: /\.css$/, loader: "style-loader!css-loader" },
+            {
+              test: /\.less$/,
+              loader: "style-loader!css-loader!less-loader",
+              exclude: [/app\/src\/components\//],
+            },
+            {
+              test: /\.less$/,
+              loader: "to-string-loader!css-loader!less-loader",
+              include: [/app\/src\/components\//],
+            },
+            {
+              test: /\.(png|svg)$/,
+              loader: "file-loader",
+              query: {
+                name: 'images/[name].[hash:8].[ext]'
+              }
+            },
+            {
+                test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
+                loader: "file-loader",
+                query: {
+                  name: 'fonts/[name].[hash:8].[ext]'
+                }
+            },
+        ]
+    },
+    externals: {
+        'electron': 'require("electron")',
+        'net': 'require("net")',
+        'remote': 'require("remote")',
+        'shell': 'require("shell")',
+        'ipc': 'require("ipc")',
+        'fs': 'require("fs")',
+        'buffer': 'require("buffer")',
+        'pty.js': 'require("pty.js")',
+        'system': '{}',
+        'file': '{}'
+    },
+    plugins: [
+        new webpack.ProvidePlugin({
+            "window.jQuery": "jquery",
+        }),
+    ]
+}
+
+
+if (!process.env.DEV) {
+    module.exports.plugins.push(new webpack.LoaderOptionsPlugin({
+        minimize: true,
+    }))
+    module.exports.plugins.push(new webpack.optimize.UglifyJsPlugin({
+        sourceMap: true,
+        mangle: false,
+    }))
+    module.exports.devtool = 'source-map'
+}