commit 98fea7b1023383b4fafe108b0170f68f15831021 Author: Eugene Pankov Date: Fri Dec 23 10:06:53 2016 +0100 . diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8930b9ab --- /dev/null +++ b/.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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..d3ce2991 --- /dev/null +++ b/.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 root@cloud.elements.tv:/mnt/elements/www/clients/ + - scp Elements-macOS-$CI_BUILD_REF_NAME.pkg root@cloud.elements.tv:/mnt/elements/www/clients/ + - scp Elements-Linux-$CI_BUILD_REF_NAME.AppImage root@cloud.elements.tv:/mnt/elements/www/clients/ + tags: + - Local diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e23198d5 --- /dev/null +++ b/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 diff --git a/README.md b/README.md new file mode 100644 index 00000000..1a4f68e7 --- /dev/null +++ b/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`` diff --git a/app/assets/bootstrap/bootstrap.less b/app/assets/bootstrap/bootstrap.less new file mode 100644 index 00000000..37c59947 --- /dev/null +++ b/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; +} diff --git a/app/assets/bootstrap/include.less b/app/assets/bootstrap/include.less new file mode 100644 index 00000000..8390e132 --- /dev/null +++ b/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"; diff --git a/app/assets/bootstrap/overrides.less b/app/assets/bootstrap/overrides.less new file mode 100644 index 00000000..2191ab51 --- /dev/null +++ b/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; + } +} diff --git a/app/assets/bootstrap/variables.less b/app/assets/bootstrap/variables.less new file mode 100644 index 00000000..66e9a726 --- /dev/null +++ b/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; diff --git a/app/assets/img/disk.icns b/app/assets/img/disk.icns new file mode 100644 index 00000000..e25108c2 Binary files /dev/null and b/app/assets/img/disk.icns differ diff --git a/app/assets/img/disk.ico b/app/assets/img/disk.ico new file mode 100644 index 00000000..a855fae8 Binary files /dev/null and b/app/assets/img/disk.ico differ diff --git a/app/assets/img/icon.png b/app/assets/img/icon.png new file mode 100644 index 00000000..23bd394c Binary files /dev/null and b/app/assets/img/icon.png differ diff --git a/app/assets/img/logo.png b/app/assets/img/logo.png new file mode 100644 index 00000000..b94ffe44 Binary files /dev/null and b/app/assets/img/logo.png differ diff --git a/app/assets/img/logo.svg b/app/assets/img/logo.svg new file mode 100644 index 00000000..8a04e838 --- /dev/null +++ b/app/assets/img/logo.svg @@ -0,0 +1,107 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/app/assets/img/shortcut.ico b/app/assets/img/shortcut.ico new file mode 100644 index 00000000..1346d1ca Binary files /dev/null and b/app/assets/img/shortcut.ico differ diff --git a/app/assets/img/tray-darwin.png b/app/assets/img/tray-darwin.png new file mode 100644 index 00000000..8c16bc6f Binary files /dev/null and b/app/assets/img/tray-darwin.png differ diff --git a/app/assets/img/tray-darwin@2x.png b/app/assets/img/tray-darwin@2x.png new file mode 100644 index 00000000..53fc0978 Binary files /dev/null and b/app/assets/img/tray-darwin@2x.png differ diff --git a/app/assets/img/tray-linux.png b/app/assets/img/tray-linux.png new file mode 100644 index 00000000..a683b350 Binary files /dev/null and b/app/assets/img/tray-linux.png differ diff --git a/app/assets/img/tray-win32.png b/app/assets/img/tray-win32.png new file mode 100644 index 00000000..a683b350 Binary files /dev/null and b/app/assets/img/tray-win32.png differ diff --git a/app/assets/img/user.png b/app/assets/img/user.png new file mode 100644 index 00000000..4b29cd32 Binary files /dev/null and b/app/assets/img/user.png differ diff --git a/app/assets/toaster-custom.less b/app/assets/toaster-custom.less new file mode 100644 index 00000000..ca071b94 --- /dev/null +++ b/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; + } + } +} diff --git a/app/index.pug b/app/index.pug new file mode 100644 index 00000000..4fc213a6 --- /dev/null +++ b/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 diff --git a/app/main.js b/app/main.js new file mode 100644 index 00000000..b66b0f05 --- /dev/null +++ b/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) +}) diff --git a/app/package.json b/app/package.json new file mode 100644 index 00000000..8fe1233b --- /dev/null +++ b/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" + } +} diff --git a/app/src/app.d.ts b/app/src/app.d.ts new file mode 100644 index 00000000..d38323eb --- /dev/null +++ b/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[]) +} diff --git a/app/src/app.module.ts b/app/src/app.module.ts new file mode 100644 index 00000000..3397dfe0 --- /dev/null +++ b/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 {} diff --git a/app/src/components/app.less b/app/src/components/app.less new file mode 100644 index 00000000..2637a416 --- /dev/null +++ b/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; +} diff --git a/app/src/components/app.pug b/app/src/components/app.pug new file mode 100644 index 00000000..0e6c983f --- /dev/null +++ b/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') + | × + 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 diff --git a/app/src/components/app.ts b/app/src/components/app.ts new file mode 100644 index 00000000..55bda90f --- /dev/null +++ b/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) + } +} diff --git a/app/src/components/checkbox.less b/app/src/components/checkbox.less new file mode 100644 index 00000000..516633e8 --- /dev/null +++ b/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; + } +} diff --git a/app/src/components/checkbox.pug b/app/src/components/checkbox.pug new file mode 100644 index 00000000..cb6b09fc --- /dev/null +++ b/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}} diff --git a/app/src/components/checkbox.ts b/app/src/components/checkbox.ts new file mode 100644 index 00000000..00282126 --- /dev/null +++ b/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 +} diff --git a/app/src/components/settingsModal.less b/app/src/components/settingsModal.less new file mode 100644 index 00000000..6954f792 --- /dev/null +++ b/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; + } + } + } +} diff --git a/app/src/components/settingsModal.pug b/app/src/components/settingsModal.pug new file mode 100644 index 00000000..2cda7c22 --- /dev/null +++ b/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 diff --git a/app/src/components/settingsModal.ts b/app/src/components/settingsModal.ts new file mode 100644 index 00000000..db079055 --- /dev/null +++ b/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() + } +} diff --git a/app/src/entry.preload.ts b/app/src/entry.preload.ts new file mode 100644 index 00000000..46839062 --- /dev/null +++ b/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' diff --git a/app/src/entry.ts b/app/src/entry.ts new file mode 100644 index 00000000..85bc7180 --- /dev/null +++ b/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) } diff --git a/app/src/global.less b/app/src/global.less new file mode 100644 index 00000000..71093e88 --- /dev/null +++ b/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; +} diff --git a/app/src/services/config.ts b/app/src/services/config.ts new file mode 100644 index 00000000..b5a602f6 --- /dev/null +++ b/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 + } +} diff --git a/app/src/services/electron.ts b/app/src/services/electron.ts new file mode 100644 index 00000000..35e29cfa --- /dev/null +++ b/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 +} diff --git a/app/src/services/hostApp.ts b/app/src/services/hostApp.ts new file mode 100644 index 00000000..5613fcbc --- /dev/null +++ b/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() + ready = new EventEmitter() + + 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() + } +} diff --git a/app/src/services/log.ts b/app/src/services/log.ts new file mode 100644 index 00000000..44a4600c --- /dev/null +++ b/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) + } +} diff --git a/app/src/services/modal.ts b/app/src/services/modal.ts new file mode 100644 index 00000000..8bd46d50 --- /dev/null +++ b/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 = (modal)._removeModalElements.bind(modal); + + (modal)._removeModalElements = () => { + (modal)._windowCmptRef.instance.windowClass = 'out' + setTimeout(() => fx(), 500) + } + setTimeout(() => { + (modal)._windowCmptRef.instance.windowClass = '' + }, 1) + + return modal + } +} diff --git a/app/src/services/notify.ts b/app/src/services/notify.ts new file mode 100644 index 00000000..cbdd16e0 --- /dev/null +++ b/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, + }) + } +} diff --git a/app/src/services/quitter.ts b/app/src/services/quitter.ts new file mode 100644 index 00000000..6c48d291 --- /dev/null +++ b/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() + } +} diff --git a/build/config.gypi b/build/config.gypi new file mode 100644 index 00000000..cc03d65a --- /dev/null +++ b/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" + } +} diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 00000000..1346d1ca Binary files /dev/null and b/build/icon.ico differ diff --git a/build/icons/1024x1024.png b/build/icons/1024x1024.png new file mode 100644 index 00000000..23bd394c Binary files /dev/null and b/build/icons/1024x1024.png differ diff --git a/build/icons/128x128.png b/build/icons/128x128.png new file mode 100644 index 00000000..a511d927 Binary files /dev/null and b/build/icons/128x128.png differ diff --git a/build/icons/16x16.png b/build/icons/16x16.png new file mode 100644 index 00000000..cb302c1f Binary files /dev/null and b/build/icons/16x16.png differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 00000000..65dd9fac Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/32x32.png b/build/icons/32x32.png new file mode 100644 index 00000000..a1dd8fe8 Binary files /dev/null and b/build/icons/32x32.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 00000000..799c58d7 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/64x64.png b/build/icons/64x64.png new file mode 100644 index 00000000..bd3f036e Binary files /dev/null and b/build/icons/64x64.png differ diff --git a/build/logo.png b/build/logo.png new file mode 100644 index 00000000..1d24636d Binary files /dev/null and b/build/logo.png differ diff --git a/build/mac/Distribution.xml b/build/mac/Distribution.xml new file mode 100644 index 00000000..f4096e3b --- /dev/null +++ b/build/mac/Distribution.xml @@ -0,0 +1,35 @@ + + + ELEMENTS Client + + + + + + + + + + + + + + + + ElementsDriver.pkg + + + + + Elements.pkg + + + + + + + + + + AFPTuner.pkg + diff --git a/build/mac/Elements.component.plist b/build/mac/Elements.component.plist new file mode 100644 index 00000000..7deefe81 --- /dev/null +++ b/build/mac/Elements.component.plist @@ -0,0 +1,20 @@ + + + + + + BundleHasStrictIdentifier + + BundleIsRelocatable + + BundleIsVersionChecked + + BundleOverwriteAction + upgrade + BundlePostInstallScriptPath + Elements.postinst.sh + RootRelativeBundlePath + Applications/ELEMENTS.app + + + diff --git a/build/mac/Elements.postinst.sh b/build/mac/Elements.postinst.sh new file mode 100755 index 00000000..e322a593 --- /dev/null +++ b/build/mac/Elements.postinst.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -e + +cat > /Library/LaunchDaemons/com.elements.VolumesFix.plist << EOF + + + + +KeepAlive + +Label +com.elements.VolumesFix +ProgramArguments + +/bin/bash +-c +sleep 3; chmod 777 /Volumes + +RunAtLoad + +StandardErrorPath +/dev/null +StandardOutPath +/dev/null +UserName +root + + +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 diff --git a/build/mac/Info.plist b/build/mac/Info.plist new file mode 100644 index 00000000..517f2542 --- /dev/null +++ b/build/mac/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + en_US + CFBundleExecutable + ELEMENTS + CFBundleIdentifier + com.syslink.elements + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${VERSION} + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLName + Elements Client + CFBundleURLSchemes + + elements-client + + + + CFBundleVersion + 619 + LSMinimumSystemVersion + 10.8.0 + LSUIElement + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + Copyright © 2016 Syslink GmbH. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + AtomApplication + + diff --git a/build/mac/codesign b/build/mac/codesign new file mode 100755 index 00000000..4124b442 Binary files /dev/null and b/build/mac/codesign differ diff --git a/build/mac/icon.icns b/build/mac/icon.icns new file mode 100644 index 00000000..07e08278 Binary files /dev/null and b/build/mac/icon.icns differ diff --git a/build/windows/build.wxs b/build/windows/build.wxs new file mode 100644 index 00000000..954b2688 --- /dev/null +++ b/build/windows/build.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + diff --git a/build/windows/elements.wxs b/build/windows/elements.wxs new file mode 100644 index 00000000..0703973a --- /dev/null +++ b/build/windows/elements.wxs @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/windows/signtool.exe b/build/windows/signtool.exe new file mode 100644 index 00000000..f2aa67e3 Binary files /dev/null and b/build/windows/signtool.exe differ diff --git a/build/windows/wix-theme.xml b/build/windows/wix-theme.xml new file mode 100644 index 00000000..e992c8a1 --- /dev/null +++ b/build/windows/wix-theme.xml @@ -0,0 +1,80 @@ + + + + + + #(loc.Caption) + Segoe UI + Segoe UI + Segoe UI + Segoe UI + Segoe UI + + + + + #(loc.HelpHeader) + #(loc.HelpText) + + + + + + + + + #(loc.OptionsHeader) + #(loc.OptionsLocationLabel) + + + + + + + #(loc.FilesInUseHeader) + #(loc.FilesInUseLabel) + + + + + + + + + + #(loc.ProgressHeader) + #(loc.ProgressLabel) + #(loc.OverallProgressPackageText) + + + + + #(loc.ModifyHeader) + + + + + + + Success + Installed successfully + Repaired successfully + Uninstalled successfully + + + + + + #(loc.FailureHeader) + Setup failed + Uninstall failed + Repair failed + #(loc.FailureHyperlinkLogText) + + #(loc.FailureRestartText) + + + + + diff --git a/package.json b/package.json new file mode 100644 index 00000000..ab093d09 --- /dev/null +++ b/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" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..a5066256 --- /dev/null +++ b/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" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..b894a4de --- /dev/null +++ b/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 + } +} diff --git a/typings.json b/typings.json new file mode 100644 index 00000000..6cdf6704 --- /dev/null +++ b/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" + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..1c5c2c83 --- /dev/null +++ b/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' +}