diff --git a/.gitignore b/.gitignore index ca31f6aa9..1075883f9 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,15 @@ nativescript/src/**/*.js nativescript/src/**/*.js.map nativescript/*.d.ts !nativescript/src/references.d.ts +electron/app +electron/config +electron/node_modules +electron/platforms +electron/src/app +electron/src/**/*.js +electron/src/**/*.js.map +electron/*.d.ts +!electron/src/references.d.ts desktop gulpfile.js gulpfile.js.map diff --git a/electron/helpers.js b/electron/helpers.js new file mode 100644 index 000000000..b62f83f56 --- /dev/null +++ b/electron/helpers.js @@ -0,0 +1,40 @@ +var path = require('path'); +var zlib = require('zlib'); + + +// Helper functions + +function hasProcessFlag(flag) { + return process.argv.join('').indexOf(flag) > -1; +} + +function gzipMaxLevel(buffer, callback) { + return zlib['gzip'](buffer, {level: 9}, callback); +} + +function root(args) { + args = Array.prototype.slice.call(arguments, 0); + return path.join.apply(path, [__dirname].concat(args)); +} + +function rootNode(args) { + args = Array.prototype.slice.call(arguments, 0); + return root.apply(path, ['node_modules'].concat(args)); +} + +function prependExt(extensions, args) { + args = args || []; + if (!Array.isArray(args)) { args = [args] } + return extensions.reduce(function(memo, val) { + return memo.concat(val, args.map(function(prefix) { + return prefix + val; + })); + }, ['']); +} + +exports.hasProcessFlag = hasProcessFlag; +exports.gzipMaxLevel = gzipMaxLevel; +exports.root = root; +exports.rootNode = rootNode; +exports.prependExt = prependExt; +exports.prepend = prependExt; \ No newline at end of file diff --git a/electron/karma.conf.js b/electron/karma.conf.js new file mode 100644 index 000000000..1774837de --- /dev/null +++ b/electron/karma.conf.js @@ -0,0 +1,123 @@ +module.exports = function (config) { + var testWebpackConfig = require('./webpack.test.js')({ env: 'test' }); + + var configuration = { + + // base path that will be used to resolve all patterns (e.g. files, exclude) + basePath: '', + + /* + * Frameworks to use + * + * available frameworks: https://npmjs.org/browse/keyword/karma-adapter + */ + frameworks: ['jasmine'], + + // list of files to exclude + exclude: [], + + client: { + captureConsole: false + }, + + /* + * list of files / patterns to load in the browser + * + * we are building the test environment in ./spec-bundle.js + */ + files: [ + { pattern: './config/spec-bundle.js', watched: false }, + { pattern: './src/assets/**/*', watched: false, included: false, served: true, nocache: false } + ], + + /* + * By default all assets are served at http://localhost:[PORT]/base/ + */ + proxies: { + "/assets/": "/base/src/assets/" + }, + + /* + * preprocess matching files before serving them to the browser + * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + */ + preprocessors: { './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] }, + + // Webpack Config at ./webpack.test.js + webpack: testWebpackConfig, + + coverageReporter: { + type: 'in-memory' + }, + + remapCoverageReporter: { + 'text-summary': null, + json: './coverage/coverage.json', + html: './coverage/html' + }, + + // Webpack please don't spam the console when running in karma! + webpackMiddleware: { + // webpack-dev-middleware configuration + // i.e. + noInfo: true, + // and use stats to turn off verbose output + stats: { + // options i.e. + chunks: false + } + }, + + /* + * test results reporter to use + * + * possible values: 'dots', 'progress' + * available reporters: https://npmjs.org/browse/keyword/karma-reporter + */ + reporters: ['mocha', 'coverage', 'remap-coverage'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + /* + * level of logging + * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + */ + logLevel: config.LOG_WARN, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + /* + * start these browsers + * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + */ + browsers: [ + 'Chrome' + ], + + customLaunchers: { + ChromeTravisCi: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + + /* + * Continuous Integration mode + * if true, Karma captures browsers, runs the tests and exits + */ + singleRun: true + }; + + if (process.env.TRAVIS) { + configuration.browsers = [ + 'ChromeTravisCi' + ]; + } + + config.set(configuration); +}; \ No newline at end of file diff --git a/electron/package.js b/electron/package.js new file mode 100644 index 000000000..8445f03b7 --- /dev/null +++ b/electron/package.js @@ -0,0 +1,58 @@ +"use strict"; + +var packager = require('electron-packager'); +const pkg = require('./package.json'); +const argv = require('minimist')(process.argv.slice(2)); +const devDeps = Object.keys(pkg.devDependencies); + +const appName = argv.name || pkg.productName; +const shouldUseAsar = argv.asar || false; +const shouldBuildAll = argv.all || false; +const arch = argv.arch || 'all'; +const platform = argv.platform || 'darwin'; + +const DEFAULT_OPTS = { + dir: './src/app', + name: appName, + asar: shouldUseAsar, + ignore: [ + ].concat(devDeps.map(name => `/node_modules/${name}($|/)`)) +}; + +const icon = './src/app/dist/assets/app-icon'; + +if (icon) { + DEFAULT_OPTS.icon = icon; +} + +pack(platform, arch, function done(err, appPath) { + console.log(err); +}); + +function pack(plat, arch, cb) { + // there is no darwin ia32 electron + if (plat === 'darwin' && arch === 'ia32') return; + + const iconObj = { + icon: DEFAULT_OPTS.icon + (() => { + let extension = '.png'; + if (plat === 'darwin') { + extension = '.icns'; + } else if (plat === 'win32') { + extension = '.ico'; + } + return extension; + })() + }; + + const opts = Object.assign({}, DEFAULT_OPTS, iconObj, { + platform: plat, + arch, + prune: true, + all: shouldBuildAll, + 'app-version': pkg.version || DEFAULT_OPTS.version, + out: `release/${plat}-${arch}` + }); + + packager(opts, cb); +} \ No newline at end of file diff --git a/electron/package.json b/electron/package.json new file mode 100644 index 000000000..078a2a7b8 --- /dev/null +++ b/electron/package.json @@ -0,0 +1,73 @@ +{ + "name": "angular-seed-advanced", + "main": "app.js", + "version": "0.0.0", + "nativescript": { + "id": "com.yourdomain.nativescript", + "tns-ios": { + "version": "2.5.0" + }, + "tns-android": { + "version": "2.5.0" + } + }, + "scripts": { + "preinstall": "mkdirp app", + "clean": "rimraf platforms node_modules lib hooks app && mkdirp app", + "ns-bundle": "ns-bundle", + "start-android-bundle": "npm run ns-bundle --android --start-app", + "start-ios-bundle": "npm run ns-bundle --ios --start-app", + "build-android-bundle": "npm run ns-bundle --android --build-app", + "build-ios-bundle": "npm run ns-bundle --ios --build-app" + }, + "dependencies": { + "@angular/animations": "~4.0.0", + "@angular/common": "~4.0.0", + "@angular/compiler": "~4.0.0", + "@angular/core": "~4.0.0", + "@angular/forms": "~4.0.0", + "@angular/http": "~4.0.0", + "@angular/platform-browser": "~4.0.0", + "@angular/platform-browser-dynamic": "~4.0.0", + "@angular/router": "~4.0.0", + "@ngrx/core": "^1.2.0", + "@ngrx/effects": "^2.0.0", + "@ngrx/store": "^2.2.1", + "@ngx-translate/core": "^6.0.1", + "@ngx-translate/http-loader": "0.0.3", + "angulartics2": "^1.6.4", + "lodash": "^4.17.4", + "nativescript-angular": "1.5.0", + "nativescript-dev-webpack": "^0.3.1", + "nativescript-theme-core": "^1.0.3", + "ngrx-store-freeze": "0.1.9", + "reflect-metadata": "^0.1.8", + "rxjs": "~5.2.0", + "tns-core-modules": "^2.5.2", + "url": "0.10.3", + "zone.js": "~0.8.2" + }, + "devDependencies": { + "@angular/compiler-cli": "~4.0.0", + "@ngrx/store-devtools": "^3.2.4", + "@ngtools/webpack": "~1.3.0", + "@types/lodash": "4.14.59", + "@types/jasmine": "^2.5.47", + "babel-traverse": "6.20.0", + "babel-types": "6.20.0", + "babylon": "6.14.1", + "copy-webpack-plugin": "~4.0.1", + "extract-text-webpack-plugin": "~2.1.0", + "fs-extra": "^2.1.2", + "htmlparser2": "^3.9.2", + "lazy": "1.0.11", + "nativescript-css-loader": "~0.26.1", + "nativescript-dev-android-snapshot": "^0.*.*", + "nativescript-dev-webpack": "^0.3.6", + "raw-loader": "~0.5.1", + "resolve-url-loader": "~2.0.2", + "typescript": "~2.1.4", + "webpack": "2.3.2", + "webpack-sources": "~0.2.3" + } +} \ No newline at end of file diff --git a/electron/src/_common.scss b/electron/src/_common.scss new file mode 100644 index 000000000..e69de29bb diff --git a/electron/src/app.scss b/electron/src/app.scss new file mode 100644 index 000000000..e69de29bb diff --git a/electron/src/app.ts b/electron/src/app.ts new file mode 100644 index 000000000..8db53d358 --- /dev/null +++ b/electron/src/app.ts @@ -0,0 +1,7 @@ +// nativescript +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +// app +import { ElectronModule } from './electron.module'; + +platformBrowserDynamic().bootstrapModule(ElectronModule); diff --git a/electron/src/assets/data.json b/electron/src/assets/data.json new file mode 100644 index 000000000..d371e9ce9 --- /dev/null +++ b/electron/src/assets/data.json @@ -0,0 +1,6 @@ +[ + "Edsger Dijkstra", + "Donald Knuth", + "Alan Turing", + "Grace Hopper" +] diff --git a/electron/src/assets/favicon/favicon-DEV.ico b/electron/src/assets/favicon/favicon-DEV.ico new file mode 100644 index 000000000..2b6c8f0a9 Binary files /dev/null and b/electron/src/assets/favicon/favicon-DEV.ico differ diff --git a/electron/src/assets/favicon/favicon-PROD.ico b/electron/src/assets/favicon/favicon-PROD.ico new file mode 100644 index 000000000..7556dacd7 Binary files /dev/null and b/electron/src/assets/favicon/favicon-PROD.ico differ diff --git a/electron/src/assets/i18n/bg.json b/electron/src/assets/i18n/bg.json new file mode 100644 index 000000000..60d246079 --- /dev/null +++ b/electron/src/assets/i18n/bg.json @@ -0,0 +1,11 @@ +{ + "HOME": "у дома", + "ABOUT": "около", + "HELLO": "Здравей", + "LOVE_TECH": "Обичам технологиите!", + "ABOUT_ADD": "Искаш повече? Добавете ги себе си!", + "ABOUT_REWARD": "За награда, тук е списък на страхотни компютърни специалисти:", + "INPUT_HINT": "Въвеждане на нов компютърен специалист...", + "ADD_BTN_TEXT": "Добави", + "SUCCESS_MSG": "Вашият разполагане на ъгловото Seed Advanced работи перфектно." +} diff --git a/electron/src/assets/i18n/en.json b/electron/src/assets/i18n/en.json new file mode 100644 index 000000000..97814db05 --- /dev/null +++ b/electron/src/assets/i18n/en.json @@ -0,0 +1,11 @@ +{ + "HOME": "Home", + "ABOUT": "About", + "HELLO": "Hello", + "LOVE_TECH": "I love technology!", + "ABOUT_ADD": "You want more? Add them yourself!", + "ABOUT_REWARD": "For reward, here is a list of awesome computer scientists:", + "INPUT_HINT": "Enter a new computer scientist...", + "ADD_BTN_TEXT": "Add", + "SUCCESS_MSG": "Your deployment of Angular Seed Advanced worked perfectly." +} diff --git a/electron/src/assets/i18n/es.json b/electron/src/assets/i18n/es.json new file mode 100644 index 000000000..37e890520 --- /dev/null +++ b/electron/src/assets/i18n/es.json @@ -0,0 +1,11 @@ +{ + "HOME": "Casa", + "ABOUT": "Acerca de", + "HELLO": "Hola", + "LOVE_TECH": "Me encanta la tecnología!", + "ABOUT_ADD": "¿Quieres más? Añadir a ti mismo!", + "ABOUT_REWARD": "Para recompensa, aquí hay una lista de científicos informáticos impresionantes:", + "INPUT_HINT": "Introduzca un nuevo científico de la computación...", + "ADD_BTN_TEXT": "Añadir", + "SUCCESS_MSG": "Su despliegue de Angular avanzada Semilla funcionó a la perfección." +} diff --git a/electron/src/assets/i18n/fr.json b/electron/src/assets/i18n/fr.json new file mode 100644 index 000000000..8d72aa98e --- /dev/null +++ b/electron/src/assets/i18n/fr.json @@ -0,0 +1,11 @@ +{ + "HOME": "Accueil", + "ABOUT": "À propos", + "HELLO": "Bonjour", + "LOVE_TECH": "J'adore la technologie !", + "ABOUT_ADD": "Vous en voulez plus? Ajoutez-les vous-même !", + "ABOUT_REWARD": "En récompense, voici une liste de géniaux informaticiens :", + "INPUT_HINT": "Ajouter un nouvel informaticien...", + "ADD_BTN_TEXT": "Ajouter", + "SUCCESS_MSG": "Votre déploiement d'Angular Seed Advanced a parfaitement fonctionné." +} diff --git a/electron/src/assets/i18n/ru.json b/electron/src/assets/i18n/ru.json new file mode 100644 index 000000000..433ec6b3c --- /dev/null +++ b/electron/src/assets/i18n/ru.json @@ -0,0 +1,11 @@ +{ + "HOME": "Главная", + "ABOUT": "Справка", + "HELLO": "Здравствуйте", + "LOVE_TECH": "Я люблю технологию!", + "ABOUT_ADD": "Хочешь больше? Добавь их самостоятельно!", + "ABOUT_REWARD": "В награду, вот список классных компьютерных ученых:", + "INPUT_HINT": "Введите нового ученого...", + "ADD_BTN_TEXT": "Добавить", + "SUCCESS_MSG": "Ваш Angular Seed Advanced развернут отлично." +} diff --git a/electron/src/assets/logo.icns b/electron/src/assets/logo.icns new file mode 100644 index 000000000..983a10d3e Binary files /dev/null and b/electron/src/assets/logo.icns differ diff --git a/electron/src/assets/logo.ico b/electron/src/assets/logo.ico new file mode 100644 index 000000000..eb28b1385 Binary files /dev/null and b/electron/src/assets/logo.ico differ diff --git a/electron/src/assets/svg/more.svg b/electron/src/assets/svg/more.svg new file mode 100644 index 000000000..1b6b1dd01 --- /dev/null +++ b/electron/src/assets/svg/more.svg @@ -0,0 +1,7 @@ + + + diff --git a/electron/src/assets/svg/smile.svg b/electron/src/assets/svg/smile.svg new file mode 100644 index 000000000..368c12e4c --- /dev/null +++ b/electron/src/assets/svg/smile.svg @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + emoticon + emote + smiley + happy + :) + :-) + + + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/electron/src/electron.module.ts b/electron/src/electron.module.ts new file mode 100644 index 000000000..d3b7728a5 --- /dev/null +++ b/electron/src/electron.module.ts @@ -0,0 +1,100 @@ +// angular +import { NgModule } from '@angular/core'; +import { APP_BASE_HREF } from '@angular/common'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { Http } from '@angular/http'; + +// libs +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import { TranslateLoader } from '@ngx-translate/core'; + +// app +import { APP_COMPONENTS, AppComponent } from './app/components/index'; +import { routes } from './app/components/app.routes'; + +// feature modules +import { CoreModule } from './app/shared/core/core.module'; +import { AppReducer } from './app/shared/ngrx/index'; +import { AnalyticsModule } from './app/shared/analytics/analytics.module'; +import { MultilingualModule, translateLoaderFactory } from './app/shared/i18n/multilingual.module'; +import { MultilingualEffects } from './app/shared/i18n/index'; +import { SampleModule } from './app/shared/sample/sample.module'; +import { NameListEffects } from './app/shared/sample/index'; + +// config +import { Config, WindowService, ConsoleService } from './app/shared/core/index'; +Config.PLATFORM_TARGET = Config.PLATFORMS.WEB; +if (String('<%= BUILD_TYPE %>') === 'dev') { + // only output console logging in dev mode + Config.DEBUG.LEVEL_4 = true; +} + +// sample config (extra) +import { AppConfig } from './app/shared/sample/services/app-config'; +import { MultilingualService } from './app/shared/i18n/services/multilingual.service'; +// custom i18n language support +MultilingualService.SUPPORTED_LANGUAGES = AppConfig.SUPPORTED_LANGUAGES; + +let routerModule = RouterModule.forRoot(routes); + +if (String('<%= TARGET_DESKTOP %>') === 'true') { + Config.PLATFORM_TARGET = Config.PLATFORMS.DESKTOP; + // desktop (electron) must use hash + routerModule = RouterModule.forRoot(routes, {useHash: true}); +} + +declare var window, console; + +// For AoT compilation to work: +export function win() { + return window; +} +export function cons() { + return console; +} + +let DEV_IMPORTS: any[] = []; + +if (String('<%= BUILD_TYPE %>') === 'dev') { + DEV_IMPORTS = [ + ...DEV_IMPORTS, + StoreDevtoolsModule.instrumentOnlyWithExtension() + ]; +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot([ + { provide: WindowService, useFactory: (win) }, + { provide: ConsoleService, useFactory: (cons) } + ]), + routerModule, + AnalyticsModule, + MultilingualModule.forRoot([{ + provide: TranslateLoader, + deps: [Http], + useFactory: (translateLoaderFactory) + }]), + SampleModule, + StoreModule.provideStore(AppReducer), + DEV_IMPORTS, + EffectsModule.run(MultilingualEffects), + EffectsModule.run(NameListEffects) + ], + declarations: [ + APP_COMPONENTS + ], + providers: [ + { + provide: APP_BASE_HREF, + useValue: '<%= APP_BASE %>' + } + ], + bootstrap: [AppComponent] +}) + +export class ElectronModule { } diff --git a/electron/src/main.desktop.ts b/electron/src/main.desktop.ts new file mode 100644 index 000000000..9a16ad174 --- /dev/null +++ b/electron/src/main.desktop.ts @@ -0,0 +1,270 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; +console.log(`Electron launching with NODE_ENV: ${process.env.NODE_ENV}`); + +// electron +const electron = require('electron'); +const app = electron.app; +const Menu: any = electron.Menu; +const shell: any = electron.shell; +// const {crashReporter} = require('electron'); +const BrowserWindow = electron.BrowserWindow; +let mainWindow: any = null; +let template: any; +let menu: any; + +// app +import { DesktopConfig } from './app/shared/electron/index'; + +// Sample +// You would need a valid `submitURL` to use +// crashReporter.start({ +// productName: 'AngularSeedAdvanced', +// companyName: 'NathanWalker', +// submitURL: 'https://github.com/NathanWalker/angular-seed-advanced', +// autoSubmit: true +// }); + +if (process.env.NODE_ENV === 'development') { + require('electron-debug')(); +} + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('ready', () => { + + // Initialize the window to our specified dimensions + mainWindow = new BrowserWindow({ width: 900, height: 620 }); + + // Tell Electron where to load the entry point from + mainWindow.loadURL('file://' + __dirname + '/index.html'); + + // Clear out the main window when the app is closed + mainWindow.on('closed', () => { + mainWindow = null; + }); + + mainWindow.webContents.on('did-navigate-in-page', (e: any, url: string) => { + console.log(`Page navigated: ${url}`); + }); + + let appTitle: string = `Angular Seed Advanced`; + + let langMenu: any = { + label: 'Language', + submenu: [] + }; + for (var lang of DesktopConfig.SUPPORTED_LANGUAGES) { + let code = lang.code; + let langOption = { + label: lang.title, + click:() => { + console.log(`Change lang: ${code}`); + mainWindow.webContents.executeJavaScript(`window.dispatchEvent(new CustomEvent('changeLang', {detail: { value: '${code}'} }));`); + } + }; + langMenu.submenu.push(langOption); + } + + let helpMenu: any = { + label: 'Help', + submenu: [{ + label: 'Learn More', + click:() => { + shell.openExternal('https://github.com/NathanWalker/angular-seed-advanced'); + } + }, { + label: 'Issues', + click:() => { + shell.openExternal('https://github.com/NathanWalker/angular-seed-advanced/issues'); + } + }, { + label: `My Amazing Parent: Minko Gechev's Angular Seed`, + click:() => { + shell.openExternal('https://github.com/mgechev/angular-seed'); + } + }, { + label: 'Angular 2', + click:() => { + shell.openExternal('https://angular.io/'); + } + }, { + label: 'Electron', + click:() => { + shell.openExternal('http://electron.atom.io/'); + } + }, { + label: 'Electron Docs', + click: () => { + shell.openExternal('https://github.com/atom/electron/tree/master/docs'); + } + }, { + label: 'Codeology Visualization', + click:() => { + shell.openExternal('http://codeology.braintreepayments.com/nathanwalker/angular-seed-advanced'); + } + }] + }; + + if (process.platform === 'darwin') { + template = [{ + label: appTitle, + submenu: [{ + label: `About ${appTitle}`, + selector: 'orderFrontStandardAboutPanel:' + }, { + type: 'separator' + }, { + label: 'Services', + submenu: [] + }, { + type: 'separator' + }, { + label: 'Hide Angular Seed Advanced', + accelerator: 'Command+H', + selector: 'hide:' + }, { + label: 'Hide Others', + accelerator: 'Command+Shift+H', + selector: 'hideOtherApplications:' + }, { + label: 'Show All', + selector: 'unhideAllApplications:' + }, { + type: 'separator' + }, { + label: 'Quit', + accelerator: 'Command+Q', + click:() => { + app.quit(); + } + }] + }, { + label: 'Edit', + submenu: [{ + label: 'Undo', + accelerator: 'Command+Z', + selector: 'undo:' + }, { + label: 'Redo', + accelerator: 'Shift+Command+Z', + selector: 'redo:' + }, { + type: 'separator' + }, { + label: 'Cut', + accelerator: 'Command+X', + selector: 'cut:' + }, { + label: 'Copy', + accelerator: 'Command+C', + selector: 'copy:' + }, { + label: 'Paste', + accelerator: 'Command+V', + selector: 'paste:' + }, { + label: 'Select All', + accelerator: 'Command+A', + selector: 'selectAll:' + }] + }, { + label: 'View', + submenu: (process.env.NODE_ENV === 'development') ? [{ + label: 'Reload', + accelerator: 'Command+R', + click:() => { + mainWindow.reload(); + } + }, { + label: 'Toggle Full Screen', + accelerator: 'Ctrl+Command+F', + click:() => { + mainWindow.setFullScreen(!mainWindow.isFullScreen()); + } + }, { + label: 'Toggle Developer Tools', + accelerator: 'Alt+Command+I', + click:() => { + mainWindow.toggleDevTools(); + } + }] : [{ + label: 'Toggle Full Screen', + accelerator: 'Ctrl+Command+F', + click:() => { + mainWindow.setFullScreen(!mainWindow.isFullScreen()); + } + }] + }, { + label: 'Window', + submenu: [{ + label: 'Minimize', + accelerator: 'Command+M', + selector: 'performMiniaturize:' + }, { + label: 'Close', + accelerator: 'Command+W', + selector: 'performClose:' + }, { + type: 'separator' + }, { + label: 'Bring All to Front', + selector: 'arrangeInFront:' + }] + }, + langMenu, + helpMenu]; + + menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); + } else { + template = [{ + label: '&File', + submenu: [{ + label: '&Open', + accelerator: 'Ctrl+O' + }, { + label: '&Close', + accelerator: 'Ctrl+W', + click:() => { + mainWindow.close(); + } + }] + }, { + label: '&View', + submenu: (process.env.NODE_ENV === 'development') ? [{ + label: '&Reload', + accelerator: 'Ctrl+R', + click:() => { + mainWindow.reload(); + } + }, { + label: 'Toggle &Full Screen', + accelerator: 'F11', + click:() => { + mainWindow.setFullScreen(!mainWindow.isFullScreen()); + } + }, { + label: 'Toggle &Developer Tools', + accelerator: 'Alt+Ctrl+I', + click:() => { + mainWindow.toggleDevTools(); + } + }] : [{ + label: 'Toggle &Full Screen', + accelerator: 'F11', + click:() => { + mainWindow.setFullScreen(!mainWindow.isFullScreen()); + } + }] + }, + langMenu, + helpMenu]; + menu = Menu.buildFromTemplate(template); + mainWindow.setMenu(menu); + } + +}); diff --git a/electron/src/package.json b/electron/src/package.json new file mode 100644 index 000000000..102631975 --- /dev/null +++ b/electron/src/package.json @@ -0,0 +1,5 @@ +{ + "name": "angular-seed-advanced", + "version": "1.0.0", + "main": "main.desktop.js" +} diff --git a/electron/src/polyfills.ts b/electron/src/polyfills.ts new file mode 100644 index 000000000..27f20b00d --- /dev/null +++ b/electron/src/polyfills.ts @@ -0,0 +1,13 @@ +import 'core-js/es6'; +import 'core-js/es7/reflect'; +require('zone.js/dist/zone.js'); +//Error['stackTraceLimit'] = Infinity; + +require('zone.js/dist/long-stack-trace-zone'); + +// RxJS +// to include every operator uncomment +// require('rxjs/Rx'); + +require('rxjs/add/operator/map'); +require('rxjs/add/operator/mergeMap'); \ No newline at end of file diff --git a/electron/src/tsconfig.json b/electron/src/tsconfig.json new file mode 100644 index 000000000..58aaff659 --- /dev/null +++ b/electron/src/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "declaration": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es6", + "dom", + "es2015.iterable" + ], + "sourceMap": true, + "pretty": true, + "noEmitHelpers": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "typeRoots": [ + "../node_modules/@types", + "../node_modules" + ], + "types": [ + "jasmine", + "node" + ] + }, + "exclude": [ + "node_modules", + "platforms", + "*.aot.ts" + ], + "compileOnSave": false +} diff --git a/electron/src/vendor.ts b/electron/src/vendor.ts new file mode 100644 index 000000000..f085d3f93 --- /dev/null +++ b/electron/src/vendor.ts @@ -0,0 +1,9 @@ +require('./vendor-platform'); + +require('reflect-metadata'); +require('@angular/platform-browser'); +require('@angular/core'); +require('@angular/common'); +require('@angular/forms'); +require('@angular/http'); +require('@angular/router'); diff --git a/electron/tools/webpack/elec-loader.js b/electron/tools/webpack/elec-loader.js new file mode 100644 index 000000000..127e57cc2 --- /dev/null +++ b/electron/tools/webpack/elec-loader.js @@ -0,0 +1,137 @@ +const fs = require('fs-extra'); +const loaderUtils = require('loader-utils');; +const _ = require('lodash'); +const template = _.template; + +const htmlCssRegexp = /\.component\.(html|css)/; +const htmlCssReplaceStr = '.component.elec.\$1'; +const serviceRegexp = /\.service\.(ts|js)/; +const serviceReplaceStr = '.service.elec.\$1'; +const componentRegexp = /\.component\.(ts|js)/; +const componentReplaceStr = '.component.elec.\$1'; +const componentFactoryRegexp = /\.component\.ngfactory\.(ts|js)/; + +const ng2TemplateUrlRegex = /templateUrl *:(.*)$/gm; +const ng2StyleUrlsRegex = /styleUrls *:(\s*\[[^\]]*?\])/g; +const ng2StringReplaceRegexp = /(['"])((?:[^\\]\\\1|.)*?)\1/g; +const ng2ModuleIdRegexp = /(moduleId *: *module\.id,)$/gm; + +function replaceViewStringsWithRequires(string) { + return string.replace(ng2StringReplaceRegexp, function (match, quote, url) { + if (url.charAt(0) !== '.') { + url = './' + url; + } + + const elecViewPath = url.replace(htmlCssRegexp, htmlCssReplaceStr); + if (fs.existsSync(elecViewPath)) { + return 'require(\'' + elecViewPath + '\')'; + } + + return 'require(\'' + url + '\')'; + }); +} + +function replaceServiceStringsWithRequires(string) { + return string.replace(ng2StringReplaceRegexp, function (match, quote, url) { + if (url.charAt(0) !== '.') { + url = './' + url; + } + + const elecSvcPath = url.replace(serviceRegexp, serviceReplaceStr); + if (fs.existsSync(elecSvcPath)) { + return 'require(\'' + elecSvcPath + '\')'; + } + + return 'require(\'' + url + '\')'; + }); +} + +function replaceComponentStringsWithRequires(string) { + return string.replace(ng2StringReplaceRegexp, function (match, quote, url) { + if (url.charAt(0) !== '.') { + url = './' + url; + } + + const elecCmpPath = url.replace(serviceRegexp, serviceReplaceStr); + if (fs.existsSync(elecCmpPath)) { + return 'require(\'' + elecCmpPath + '\')'; + } + + return 'require(\'' + url + '\')'; + }); +} + +function injectTemplateVariables(loaderContext, source) { + // Inject template variables via lodash.template + // Note: We only support the '' syntax (default matches and breaks on es6 string literals). + const query = loaderUtils.getOptions(loaderContext); + + const tpl = template(source, { + interpolate: /<%=([\s\S]+?)%>/g, + }); + + return tpl(query.data); +} + +module.exports = function tnsLoader(source) { + if (this.resourcePath.match(elecSvcPath)) { + // If a electron-version of the service file exists, replace the source with the content of that file. + const elecSvcPath = this.resourcePath.replace(serviceRegexp, serviceReplaceStr); + if (fs.existsSync(elecSvcPath)) { + const elecSvcSource = fs.readFileSync(elecSvcPath, 'UTF-8'); + source = 'module.exports == ' + JSON.stringify(elecSvcSource); + } + } + if (this.resourcePath.math(elecCmpPath)) { + // If a electron-version of the component file exists, replace the source with the content of that file. + const elecCmpPath = this.resourcePath.replace(componentRegexp, componentReplaceStr); + if (fs.existsSync(elecCmpPath)) { + const elecCmpSource = fs.readFileSync(elecCmpPath, 'UTF-8'); + source = 'module.exports = ' + JSON.stringify(elecCmpSource); + //Replace module.id needed?? + const styleProperty = 'styles'; + const templateProperty = 'template'; + + source = source.replace(ng2TemplateUrlRegex, function replaceTemplateUrl(match, url) { + return templateProperty + ':' + replaceStringsWithRequires(url); + }) + .replace(ng2StyleUrlsRegex, function replaceStyleUrls(match, urls) { + return styleProperty + ':' + replaceStringsWithRequires(urls); + }) + .replace(ng2ModuleIdRegexp, function moduleId(match, moduleId) { + return '/* ' + moduleId + ' */'; + }); + } + } + if (this.resourcePath.match(htmlCssRegexp)) { + // If a tns-version of the html/css file exists, replace the source with the content of that file. + const elecViewPath = this.resourcePath.replace(htmlCssRegexp, htmlCssReplaceStr); + if (fs.existsSync(elecViewPath)) { + const elecViewSource = fs.readFileSync(elecViewPath, 'UTF-8'); + source = 'module.exports = ' + JSON.stringify(elecViewSource); + } + + } else if (this.resourcePath.match(componentRegexp)) { + // For ng2 components cnnvert: + // styleUrls = ['file1', ..., fileN] => styles = [require('file1'), ..., require('fileN')] + // templateUrl: 'file' => template: require('file') + // + // Removes moduleId + const styleProperty = 'styles'; + const templateProperty = 'template'; + + source = source.replace(ng2TemplateUrlRegex, function replaceTemplateUrl(match, url) { + return templateProperty + ':' + replaceStringsWithRequires(url); + }) + .replace(ng2StyleUrlsRegex, function replaceStyleUrls(match, urls) { + return styleProperty + ':' + replaceStringsWithRequires(urls); + }) + .replace(ng2ModuleIdRegexp, function moduleId(match, moduleId) { + return '/* ' + moduleId + ' */'; + }); + } else if (this.resourcePath.match(componentFactoryRegexp)) { + // TODO: should/could we do something with the NgFactory files? + } + + return injectTemplateVariables(this, source); +}; diff --git a/electron/tsconfig.aot.json b/electron/tsconfig.aot.json new file mode 100644 index 000000000..b1221e9af --- /dev/null +++ b/electron/tsconfig.aot.json @@ -0,0 +1,35 @@ +{ + "extend": "./tsconfig", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "ui/*": ["node_modules/tns-core-modules/ui/*"], + "platform": ["node_modules/tns-core-modules/platform"], + "image-source": ["node_modules/tns-core-modules/image-source"], + "xml": ["node_modules/tns-core-modules/xml"], + "xhr": ["node_modules/tns-core-modules/xhr"], + "text": ["node_modules/tns-core-modules/text"], + "data": ["node_modules/tns-core-modules/data"], + "fetch": ["node_modules/tns-core-modules/fetch"], + "trace": ["node_modules/tns-core-modules/trace"], + "fps-meter": ["node_modules/tns-core-modules/fps-meter"], + "color": ["node_modules/tns-core-modules/color"], + "application-settings": ["node_modules/tns-core-modules/application-settings"], + "http": ["node_modules/tns-core-modules/http"], + "camera": ["node_modules/tns-core-modules/camera"], + "console": ["node_modules/tns-core-modules/console"], + "timer": ["node_modules/tns-core-modules/timer"], + "utils": ["node_modules/tns-core-modules/utils"], + "location": ["node_modules/tns-core-modules/location"], + "file-system": ["node_modules/tns-core-modules/file-system"], + "application": ["node_modules/tns-core-modules/application"], + "image-asset": ["node_modules/tns-core-modules/image-asset"], + "connectivity": ["node_modules/tns-core-modules/connectivity"], + "globals": ["node_modules/tns-core-modules/globals"] + } + }, + "angularCompilerOptions": { + "skipMetadataEmit": true, + "genDir": "./" + } +} diff --git a/electron/tsconfig.json b/electron/tsconfig.json new file mode 100644 index 000000000..7be367f89 --- /dev/null +++ b/electron/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "strictNullChecks": false, + "baseUrl": "./src", + "paths": {}, + "lib": [ + "dom", + "es6" + ], + "types": [ + "node" + ], + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "dist" + ], + "awesomeTypescriptLoaderOptions": { + "forkChecker": true, + "useWebpackText": true + }, + "compileOnSave": false, + "buildOnSave": false, + "atom": { + "rewriteTsconfig": false + } +} diff --git a/electron/tslint.json b/electron/tslint.json new file mode 100644 index 000000000..633ff8b1e --- /dev/null +++ b/electron/tslint.json @@ -0,0 +1,80 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "directive-selector-name": [true, "camelCase"], + "component-selector-name": [true, "kebab-case"], + "directive-selector-type": [true, "attribute"], + "component-selector-type": [true, "element"], + "directive-selector-prefix": [true, "ae"], + "component-selector-prefix": [true, "ae"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-attribute-parameter-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "no-forward-ref" :true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "pipe-naming": [true, "camelCase", "sg"], + "component-class-suffix": true, + "directive-class-suffix": true, + "import-destructuring-spacing": true, + "class-name": true, + "curly": false, + "eofline": true, + "indent": [ + true, + "spaces" + ], + "max-line-length": [ + true, + 300 + ], + "member-ordering": [ + true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-construct": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "trailing-comma": true, + "no-trailing-whitespace": false, + "no-unused-expression": true, + "no-unused-variable": false, + "no-unreachable": true, + "no-use-before-declare": true, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": true, + "triple-equals": [ + false, + "allow-null-check" + ], + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} diff --git a/electron/webpack.config.js b/electron/webpack.config.js new file mode 100644 index 000000000..d0dd7d48c --- /dev/null +++ b/electron/webpack.config.js @@ -0,0 +1,196 @@ +var webpack = require('webpack'); +//var nsWebpack = require('nativescript-dev-webpack'); +//var nativescriptTarget = require('nativescript-dev-webpack/nativescript-target'); +var path = require('path'); +var CopyWebpackPlugin = require('copy-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +//var AotPlugin = require('@ngtools/webpack').AotPlugin; + +module.exports = function(platform, destinationApp) { + if (!destinationApp) { + //Default destination inside platforms//... + destinationApp = nsWebpack.getAppPath(platform); + } + + // Setup the tnsLoader + var elecLoader = { + loader: 'elec-loader', + options: { + // data file is for now generated by the 'gulp build.js.tns'-task. + //data: require('./app/build-config.json'), + }, + }; + + var entry = {}; + //Discover entry module from package.json + entry.bundle = './app' + //Vendor entry with third party libraries. + entry.vendor = './vendor'; + //app.css bundle + entry['app.css'] = './app.css'; + + var plugins = [ + new ExtractTextPlugin('app.css'), + //Vendor libs go to the vendor.js chunk + new webpack.optimize.CommonsChunkPlugin({ + name: ['vendor'] + }), + //Define useful constants like TNS_WEBPACK + // new webpack.DefinePlugin({ + // global: 'global', + // __dirname: '__dirname', + // 'global.TNS_WEBPACK': 'true', + // }), + //Copy assets to out dir. Add your own globs as needed. + new CopyWebpackPlugin([{ + from: 'css/**' + }, { + from: 'fonts/**' + }, { + from: '**/*.jpg' + }, { + from: '**/*.png' + }, { + from: '**/*.xml' + }, { + from: 'assets', + to: 'assets', + type: 'dir', + }, ], { + ignore: [] + }), + //Generate a bundle starter script and activate it in package.json + // new nsWebpack.GenerateBundleStarterPlugin([ + // './vendor', + // './bundle', + // ]), + + // Exclude explicitly required but never declared in XML elements. + // Loader nativescript-dev-webpack/tns-xml-loader should be added for *.xml/html files. + //new nsWebpack.ExcludeUnusedElementsPlugin(), + + //Angular AOT compiler + // new AotPlugin({ + // tsConfigPath: 'tsconfig.aot.json', + // entryModule: path.resolve(__dirname, 'app/electron.module#ElectronModule'), + // typeChecking: false + // }), + // new nsWebpack.StyleUrlResolvePlugin({ + // platform + // }), + ]; + +// if (process.env.npm_config_uglify) { +// //Work around an Android issue by setting compress = false +// var compress = platform !== 'android'; +// plugins.push(new webpack.optimize.UglifyJsPlugin({ +// mangle: { +// except: nsWebpack.uglifyMangleExcludes, +// }, +// compress: compress, +// })); +// } + + return { + context: path.resolve('./src/app'), + target: 'electron-renderer', + entry: entry, + output: { + pathinfo: true, + path: path.resolve('./app'), + libraryTarget: 'commonjs2', + filename: '[name].js', + }, + resolve: { + //Resolve platform-specific modules like module.android.js + extensions: [ + '.aot.ts', + '.ts', + '.js', + '.css', + ], + //Resolve {N} system modules from tns-core-modules + modules: [ + 'node_modules/tns-core-modules', + 'node_modules' + ] + }, + resolveLoader: { + alias: { + 'elec-loader': path.join(__dirname, 'tools', 'webpack', 'elec-loader.js'), + } + }, + // node: { + // //Disable node shims that conflict with NativeScript + // 'http': false, + // 'timers': false, + // 'setImmediate': false, + // }, + module: { + rules: [{ + test: /\.html$|\.xml$/, + use: [ + elecLoader, + 'raw-loader', + //'nativescript-dev-webpack/tns-xml-loader' + ] + }, + // Root app.css file gets extracted with bundled dependencies + { + test: /app\.css$/, + use: ExtractTextPlugin.extract([ + 'resolve-url-loader', + //'nativescript-css-loader', + //'nativescript-dev-webpack/platform-css-loader', + ]), + }, + // Other CSS files get bundled using the raw loader + { + test: /\.css$/, + exclude: /app\.css$/, + use: [ + elecLoader, + 'raw-loader', + ] + }, + // Compile TypeScript files with ahead-of-time compiler. + { + test: /\.ts$/, + use: [ + elecLoader, + //'nativescript-dev-webpack/tns-aot-loader', + //'@ngtools/webpack', + ] + }, + + /* + * Json loader support for *.json files. + * + * See: https://github.com/webpack/json-loader + */ + { + test: /\.json$/, + use: 'json-loader' + }, + + // SASS support + { + test: /\.scss$/, + use: [ + 'raw-loader', + 'resolve-url-loader', + 'sass-loader' + ] + }, + + /* File loader for supporting images, for example, in CSS files. + */ + { + test: /\.(jpg|png|gif)$/, + use: 'file-loader' + } + ] + }, + plugins: plugins, + }; +}; diff --git a/electron/webpack.test.js b/electron/webpack.test.js new file mode 100644 index 000000000..2b4381819 --- /dev/null +++ b/electron/webpack.test.js @@ -0,0 +1,248 @@ +const helpers = require('./helpers'); + +/** + * Webpack Plugins + */ +const ProvidePlugin = require('webpack/lib/ProvidePlugin'); +const DefinePlugin = require('webpack/lib/DefinePlugin'); +const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); +const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'); + +/** + * Webpack Constants + */ +const ENV = process.env.ENV = process.env.NODE_ENV = 'test'; + +/** + * Webpack configuration + * + * See: http://webpack.github.io/docs/configuration.html#cli + */ +module.exports = function (options) { + return { + + /** + * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack + * + * Do not change, leave as is or it wont work. + * See: https://github.com/webpack/karma-webpack#source-maps + */ + devtool: 'inline-source-map', + + /** + * Options affecting the resolving of modules. + * + * See: http://webpack.github.io/docs/configuration.html#resolve + */ + resolve: { + + /** + * An array of extensions that should be used to resolve modules. + * + * See: http://webpack.github.io/docs/configuration.html#resolve-extensions + */ + extensions: ['.ts', '.js'], + + /** + * Make sure root is src + */ + modules: [helpers.root('src'), 'node_modules'] + + }, + + /** + * Options affecting the normal modules. + * + * See: http://webpack.github.io/docs/configuration.html#module + * + * 'use:' revered back to 'loader:' as a temp. workaround for #1188 + * See: https://github.com/AngularClass/angular2-webpack-starter/issues/1188#issuecomment-262872034 + */ + module: { + + rules: [ + + /** + * Source map loader support for *.js files + * Extracts SourceMaps for source files that as added as sourceMappingURL comment. + * + * See: https://github.com/webpack/source-map-loader + */ + { + enforce: 'pre', + test: /\.js$/, + loader: 'source-map-loader', + exclude: [ + // these packages have problems with their sourcemaps + helpers.root('node_modules/rxjs'), + helpers.root('node_modules/@angular') + ] + }, + + /** + * Typescript loader support for .ts and Angular 2 async routes via .async.ts + * + * See: https://github.com/s-panferov/awesome-typescript-loader + */ + { + test: /\.ts$/, + use: [ + { + loader: 'awesome-typescript-loader', + query: { + // use inline sourcemaps for "karma-remap-coverage" reporter + sourceMap: false, + inlineSourceMap: true, + compilerOptions: { + + // Remove TypeScript helpers to be injected + // below by DefinePlugin + removeComments: true + + } + }, + }, + 'angular2-template-loader' + ], + exclude: [/\.e2e\.ts$/] + }, + + /** + * Json loader support for *.json files. + * + * See: https://github.com/webpack/json-loader + */ + { + test: /\.json$/, + loader: 'json-loader', + exclude: [helpers.root('src/index.html')] + }, + + /** + * Raw loader support for *.css files + * Returns file content as string + * + * See: https://github.com/webpack/raw-loader + */ + { + test: /\.css$/, + loader: ['to-string-loader', 'css-loader'], + exclude: [helpers.root('src/index.html')] + }, + + /** + * Raw loader support for *.scss files + * + * See: https://github.com/webpack/raw-loader + */ + { + test: /\.scss$/, + loader: ['raw-loader', 'sass-loader'], + exclude: [helpers.root('src/index.html')] + }, + + /** + * Raw loader support for *.html + * Returns file content as string + * + * See: https://github.com/webpack/raw-loader + */ + { + test: /\.html$/, + loader: 'raw-loader', + exclude: [helpers.root('src/index.html')] + }, + + /** + * Instruments JS files with Istanbul for subsequent code coverage reporting. + * Instrument only testing sources. + * + * See: https://github.com/deepsweet/istanbul-instrumenter-loader + */ + { + enforce: 'post', + test: /\.(js|ts)$/, + loader: 'istanbul-instrumenter-loader', + include: helpers.root('src'), + exclude: [ + /\.(e2e|spec)\.ts$/, + /node_modules/ + ] + } + + ] + }, + + /** + * Add additional plugins to the compiler. + * + * See: http://webpack.github.io/docs/configuration.html#plugins + */ + plugins: [ + + /** + * Plugin: DefinePlugin + * Description: Define free variables. + * Useful for having development builds with debug logging or adding global constants. + * + * Environment helpers + * + * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin + */ + // NOTE: when adding more properties make sure you include them in custom-typings.d.ts + new DefinePlugin({ + 'ENV': JSON.stringify(ENV), + 'HMR': false, + 'process.env': { + 'ENV': JSON.stringify(ENV), + 'NODE_ENV': JSON.stringify(ENV), + 'HMR': false, + } + }), + + /** + * Plugin: ContextReplacementPlugin + * Description: Provides context to Angular's use of System.import + * + * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin + * See: https://github.com/angular/angular/issues/11580 + */ + new ContextReplacementPlugin( + // The (\\|\/) piece accounts for path separators in *nix and Windows + /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, + helpers.root('src'), // location of your src + { + // your Angular Async Route paths relative to this root directory + } + ), + + /** + * Plugin LoaderOptionsPlugin (experimental) + * + * See: https://gist.github.com/sokra/27b24881210b56bbaff7 + */ + new LoaderOptionsPlugin({ + debug: false, + options: { + // legacy options go here + } + }), + + ], + /** + * Include polyfills or mocks for various node stuff + * Description: Node configuration + * + * See: https://webpack.github.io/docs/configuration.html#node + */ + node: { + global: true, + process: false, + crypto: 'empty', + module: false, + clearImmediate: false, + setImmediate: false + } + + }; +} diff --git a/nativescript/package.json b/nativescript/package.json index 813ea1c61..078a2a7b8 100644 --- a/nativescript/package.json +++ b/nativescript/package.json @@ -70,4 +70,4 @@ "webpack": "2.3.2", "webpack-sources": "~0.2.3" } -} +} \ No newline at end of file diff --git a/nativescript/src/App_Resources/Android/drawable-hdpi/icon.png b/nativescript/src/App_Resources/Android/drawable-hdpi/icon.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/Android/drawable-ldpi/icon.png b/nativescript/src/App_Resources/Android/drawable-ldpi/icon.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/Android/drawable-mdpi/icon.png b/nativescript/src/App_Resources/Android/drawable-mdpi/icon.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50.png b/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png b/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57.png b/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png b/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72.png b/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72.png old mode 100755 new mode 100644 diff --git a/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png b/nativescript/src/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png old mode 100755 new mode 100644 diff --git a/package.json b/package.json index 114eabbe6..8b6913dd4 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "karma": "karma", "karma.start": "karma start", "nativescript-install": "cd nativescript && npm install", + "electron-install": "cd electron && npm install", "postinstall": "gulp check.versions && gulp build.bundle.rxjs && npm prune && gulp webdriver && npm run nativescript-install && node tools/install.js", "reinstall": "npm cache clean && npm install", "serve.coverage": "gulp serve.coverage --color", @@ -43,7 +44,7 @@ "serve.prod.rollup.aot": "gulp serve.prod.rollup.aot --color --env-config prod --build-type prod", "start": "gulp serve.dev --color", "start.deving": "gulp start.deving --color", - "start.desktop": "gulp desktop && NODE_ENV=development electron ./dist/dev", + "start.desktop": "npm run gulp build.elec && cd electron && electron ./app/app", "start.livesync.desktop": "gulp desktop && NODE_ENV=development gulp desktop.watch", "start.livesync.desktop.windows": "gulp desktop && SET NODE_ENV=development && gulp desktop.watch", "start.desktop.windows": "gulp desktop && SET NODE_ENV=development && electron ./dist/dev", @@ -192,6 +193,7 @@ "reflect-metadata": "^0.1.8", "rxjs": "~5.2.0", "systemjs": "0.19.41", + "webpack-target-electron-renderer": "^0.4.0", "zone.js": "^0.8.4" } } diff --git a/src/client/app/components/about/about.component.html b/src/client/app/components/about/about.component.html old mode 100755 new mode 100644 diff --git a/src/client/app/components/app.component.html b/src/client/app/components/app.component.html old mode 100755 new mode 100644 diff --git a/src/client/app/components/home/home.component.html b/src/client/app/components/home/home.component.html old mode 100755 new mode 100644 diff --git a/src/client/app/components/home/home.component.scss b/src/client/app/components/home/home.component.scss old mode 100755 new mode 100644 diff --git a/src/client/app/shared/sample/components/navbar.component.css b/src/client/app/shared/sample/components/navbar.component.scss similarity index 100% rename from src/client/app/shared/sample/components/navbar.component.css rename to src/client/app/shared/sample/components/navbar.component.scss diff --git a/src/client/app/shared/sample/components/toolbar.component.css b/src/client/app/shared/sample/components/toolbar.component.scss similarity index 100% rename from src/client/app/shared/sample/components/toolbar.component.css rename to src/client/app/shared/sample/components/toolbar.component.scss diff --git a/tools/config/seed-advanced.config.ts b/tools/config/seed-advanced.config.ts index c860018d0..060dc6de4 100644 --- a/tools/config/seed-advanced.config.ts +++ b/tools/config/seed-advanced.config.ts @@ -21,9 +21,22 @@ export class SeedAdvancedConfig extends SeedConfig { ANALYTICS_TRACKING_ID: '', }; - /** - * Holds added packages for desktop build. + /** + * The base folder of the electron application source files. + * @type {string} */ + ELECTRON_BASE_DIR = 'electron'; + + ELECTRON_APP_SRC = `${this.ELECTRON_BASE_DIR}/${this.srcSubdir}`; + + ELECTRON_APP_DEST = `${this.ELECTRON_BASE_DIR}/${this.destSubdir}`; + + ELECTRON_CONFIG = { + ANALYTICS_TRACKING_ID: '', + }; + /** + * Holds added packages for desktop build. + */ DESKTOP_PACKAGES: ExtendPackages[] = []; constructor() { @@ -47,7 +60,7 @@ export class SeedAdvancedConfig extends SeedConfig { if (this.TARGET_MOBILE_HYBRID) { // Perhaps Ionic or Cordova // This is not implemented in the seed but here to show you way forward if you wanted to add - bootstrap = 'main.mobile.hybrid'; + bootstrap = 'main.mobile.hybrid'; } if (argv['analytics']) { @@ -149,10 +162,10 @@ export class SeedAdvancedConfig extends SeedConfig { * Need to duplicate this in the project.config.ts to * pick up packages there too. */ - this.DESKTOP_PACKAGES = [ + this.DESKTOP_PACKAGES = [ ...this.DESKTOP_PACKAGES, ...additionalPackages, - ]; + ]; this.addPackagesBundles(additionalPackages); diff --git a/tools/config/seed.tasks.json b/tools/config/seed.tasks.json index 48f18c981..46695c915 100644 --- a/tools/config/seed.tasks.json +++ b/tools/config/seed.tasks.json @@ -82,6 +82,12 @@ "build.js.tns" ], + "build.elec": [ + "build.assets.elec", + "build.elec_html_css", + "build.js.elec" + ], + "build.prod.tns": [ "clean.tns", "tslint.tns", diff --git a/tools/install.js b/tools/install.js index 09274f59c..6a42f09d2 100644 --- a/tools/install.js +++ b/tools/install.js @@ -12,6 +12,7 @@ var path = require('path'); var webAppPath = '../src/client/app'; var webAssetsPath = '../src/client/assets'; var nativescriptAppPath = '../nativescript/src/app/'; +var electronAppPath = '../electron/src/app/'; // Root SymLink Code for Windows if (process.argv.length > 2) { @@ -33,6 +34,9 @@ try { if (fs.existsSync(resolve(nativescriptAppPath))) { fs.unlinkSync(resolve(nativescriptAppPath)); } + if (fs.existsSync(resolve(electronAppPath))) { + fs.unlinkSync(resolve(electronAppPath)); + } } catch (err) { } @@ -105,6 +109,10 @@ function createSymLink() { console.log("Attempting to Symlink", webAppPath, nativescriptAppPath); } fs.symlinkSync(resolve(webAppPath), resolve(nativescriptAppPath), 'junction'); + if (debugging) { + console.log("Attempting to Symlink", webAppPath, electronAppPath); + } + fs.symlinkSync(resolve(webAppPath), resolve(electronAppPath), 'junction'); } /** diff --git a/tools/tasks/project/desktop.build.ts b/tools/tasks/project/desktop.build.ts index 6e91b69cb..96c7caa2a 100644 --- a/tools/tasks/project/desktop.build.ts +++ b/tools/tasks/project/desktop.build.ts @@ -1,17 +1,73 @@ import * as gulp from 'gulp'; -import { join } from 'path'; -var newer = require('gulp-newer'); +import * as gulpLoadPlugins from 'gulp-load-plugins'; +import * as merge from 'merge-stream'; +import * as fs from 'fs'; +import * as path from 'path'; import Config from '../../config'; +import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; +import { TypeScriptTask } from '../typescript_task'; -export = () => { - let src = [ - join(Config.APP_SRC, 'package.json') - ]; - return gulp.src(src) - .pipe(newer({ - dest: Config.APP_DEST, - map: function(path: String) { return path.replace('.ts', '.js').replace('.scss', '.css'); } - })) - .pipe(gulp.dest(Config.APP_DEST)); -}; +const plugins = gulpLoadPlugins(); + +const jsonSystemConfig = JSON.stringify(Config.ELECTRON_CONFIG); + +/** + * Executes the build process, transpiling the TypeScript files (except the spec and e2e-spec files) for the development + * environment. + */ +export = + class BuildJsDev extends TypeScriptTask { + run() { + const src = [ + '**/*.ts', + 'app/**/*.ts', + '!**/*.spec.ts', + '!app/**/*.spec.ts', + '!**/*.e2e-spec.ts', + '!app/**/*.e2e-spec.ts', + '!app/shared/test/**/*', + `!**/${Config.NG_FACTORY_FILE}.ts`, + ]; + + const tsProject = makeTsProject({}, Config.ELECTRON_APP_SRC); + + const result = gulp.src([ + ...src, + '!**/*.aot.ts', + ], { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(plugins.sourcemaps.init()) + .pipe(tsProject()); + + const template = (Object).assign( + new TemplateLocalsBuilder().withStringifiedSystemConfigDev().build(), { + SYSTEM_CONFIG_ELECTRON: jsonSystemConfig + }, + ); + + const transpiled = result.js + .pipe(plugins.sourcemaps.write()) + // Use for debugging with Webstorm/IntelliJ + // https://github.com/mgechev/angular-seed/issues/1220 + // .pipe(plugins.sourcemaps.write('.', { + // includeContent: false, + // sourceRoot: (file: any) => + // relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC + // })) + .pipe(plugins.template(template)) + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); + + const copy = gulp.src(src, { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); + + fs.writeFileSync(path.join(Config.ELECTRON_APP_DEST, 'build-config.json'), JSON.stringify(template)); + + return merge(transpiled, copy); + } + }; diff --git a/tools/tasks/seed/build.assets.elec.ts b/tools/tasks/seed/build.assets.elec.ts new file mode 100644 index 000000000..43a3a24e7 --- /dev/null +++ b/tools/tasks/seed/build.assets.elec.ts @@ -0,0 +1,45 @@ +import * as gulp from 'gulp'; +import * as merge from 'merge-stream'; +import { join } from 'path'; +import * as newer from 'gulp-newer'; + +import { AssetsTask } from '../assets_task'; +import Config from '../../config'; + +function copyFiles(paths: string[], subdir: string) { + const dest = join(Config.ELECTRON_APP_SRC, subdir); + + return gulp.src(paths) + .pipe(newer(dest)) + .pipe(gulp.dest(dest)); +} + +function copyAssets() { + const paths: string[] = [ + join(Config.APP_SRC, 'assets', '**'), + '!' + join(Config.APP_SRC, 'assets', 'icons', '**', '*'), + ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; })); + + return copyFiles(paths, 'assets'); +} + +function copyAppFonts() { + const paths: string[] = [ + join(Config.ELECTRON_APP_SRC, 'fonts', '**', '*.otf'), + join(Config.ELECTRON_APP_SRC, 'fonts', '**', '*.ttf'), + ]; + + return copyFiles(paths, 'fonts'); +} + +export = + class BuildElectronAssetsTask extends AssetsTask { + run() { + return merge( + copyAssets(), + copyAppFonts(), + copyFiles([join(Config.ELECTRON_APP_SRC, 'package.json')], ''), + ); + } + }; + diff --git a/tools/tasks/seed/build.elec_html_css.ts b/tools/tasks/seed/build.elec_html_css.ts new file mode 100644 index 000000000..83ee4ba98 --- /dev/null +++ b/tools/tasks/seed/build.elec_html_css.ts @@ -0,0 +1,91 @@ +import * as gulp from 'gulp'; +import * as gulpLoadPlugins from 'gulp-load-plugins'; +import * as merge from 'merge-stream'; +import * as util from 'gulp-util'; +import * as rename from 'gulp-rename'; + +import Config from '../../config'; +import { CssTask } from '../css_task'; + +const plugins = gulpLoadPlugins(); +const reportPostCssError = (e: any) => util.log(util.colors.red(e.message)); + +function renamer() { + return rename((path: any) => { + path.basename = path.basename.replace(/\.elec/, ''); + }); +} + +function prepareTemplates() { + return gulp.src([ + '**/*.html', + 'app/**/*.html', + '!app/**/*.component.html', + ], { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(renamer()) + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); +} + +function processComponentStylesheets() { + return Config.ENABLE_SCSS ? + merge( + processComponentScss(), + processComponentCss()) + : + processComponentCss(); +} + +function processComponentCss() { + return gulp.src([ + '**/*.css', + 'app/**/*.css', + '!app/**/*.component.css', + ], { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(renamer()) + //.on('error', reportPostCssError) // Causes Property 'pipe' does not exist on type 'EventEmitter'. + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); +} + +/** + * Process scss files referenced from Angular component `styleUrls` metadata + */ +function processComponentScss() { + const stream = gulp.src([ + '**/*.scss', + 'app/**/*.scss', + '!app/**/*.component.scss', + ], { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(plugins.sourcemaps.init()) + .pipe(plugins.sass(Config.getPluginConfig('gulp-sass-tns')).on('error', plugins.sass.logError)) + .pipe(plugins.sourcemaps.write()); + + return stream + .pipe(renamer()) + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); +} + +export = + class BuildElectronCSS extends CssTask { + shallRun(files: String[]) { + // Only run if tns-resources + return files.some((f) => + // tns.html, tns.scss or tns.css under nativescript/src/app + (f.indexOf('electron/src/app') !== -1 && !!f.match(/\.elec\.(s?css|html)$/)) || + // .html, .scss or .css NOT under nativescript/src/app + (f.indexOf('electron/src/app') === -1 && !!f.match(/\.(s?css|html)$/)) + ); + } + + run() { + return merge(processComponentStylesheets(), prepareTemplates()); + } + }; diff --git a/tools/tasks/seed/build.js.elec.ts b/tools/tasks/seed/build.js.elec.ts new file mode 100644 index 000000000..6d5123d1e --- /dev/null +++ b/tools/tasks/seed/build.js.elec.ts @@ -0,0 +1,75 @@ +import * as gulp from 'gulp'; +import * as gulpLoadPlugins from 'gulp-load-plugins'; +import * as merge from 'merge-stream'; +import * as fs from 'fs'; +import * as path from 'path'; + +import Config from '../../config'; +import { makeTsProject, TemplateLocalsBuilder } from '../../utils'; +import { TypeScriptTask } from '../typescript_task'; + +const plugins = gulpLoadPlugins(); + +const jsonSystemConfig = JSON.stringify(Config.ELECTRON_CONFIG); + +/** + * Executes the build process, transpiling the TypeScript files (except the spec and e2e-spec files) for the development + * environment. + */ +export = + class BuildJsDev extends TypeScriptTask { + run() { + const src = [ + '**/*.ts', + 'app/**/*.ts', + '!**/*.spec.ts', + '!app/**/*.spec.ts', + '!**/*.e2e-spec.ts', + '!app/**/*.e2e-spec.ts', + '!app/shared/test/**/*', + `!**/${Config.NG_FACTORY_FILE}.ts`, + ]; + + const tsProject = makeTsProject({}, Config.ELECTRON_APP_SRC); + + const result = gulp.src([ + ...src, + '!**/*.aot.ts', + ], { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(plugins.sourcemaps.init()) + .pipe(tsProject()); + + const template = (Object).assign( + new TemplateLocalsBuilder().withStringifiedSystemConfigDev().build(), { + SYSTEM_CONFIG_TNS: jsonSystemConfig + }, + ); + + const transpiled = result.js + .pipe(plugins.sourcemaps.write()) + // Use for debugging with Webstorm/IntelliJ + // https://github.com/mgechev/angular-seed/issues/1220 + // .pipe(plugins.sourcemaps.write('.', { + // includeContent: false, + // sourceRoot: (file: any) => + // relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC + // })) + .pipe(plugins.template(template)) + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); + + const copy = gulp.src(src, { + base: Config.ELECTRON_APP_SRC, + cwd: Config.ELECTRON_APP_SRC, + }) + .pipe(gulp.dest(Config.ELECTRON_APP_DEST)); + + fs.writeFileSync(path.join(Config.ELECTRON_APP_DEST, 'build-config.json'), JSON.stringify(template)); + + return merge(transpiled, copy); + } + }; + + diff --git a/tools/tasks/seed/build.tns_html_css.ts b/tools/tasks/seed/build.tns_html_css.ts index c695cd7f5..c1460a04f 100644 --- a/tools/tasks/seed/build.tns_html_css.ts +++ b/tools/tasks/seed/build.tns_html_css.ts @@ -48,7 +48,7 @@ function processComponentCss() { cwd: Config.TNS_APP_SRC, }) .pipe(renamer()) - .on('error', reportPostCssError) + //.on('error', reportPostCssError) // Causes Property 'pipe' does not exist on type 'EventEmitter'. .pipe(gulp.dest(Config.TNS_APP_DEST)); }