diff --git a/.eslintrc.js b/.eslintrc.js index 5b09e9579..5a45264d2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,7 +17,7 @@ module.exports = { '@typescript-eslint/no-parameter-properties': 'off', '@typescript-eslint/no-use-before-define': 'off', 'import/no-unresolved': 'off', - camelcase: 'warn', + 'camelcase': 'warn', 'simple-import-sort/sort': 'warn', 'sort-imports': 'off', 'import/first': 'warn', @@ -42,5 +42,11 @@ module.exports = { ], }, }, + { + files: '**/*.js', + env: { + node: true, + }, + }, ], }; diff --git a/angular.json b/angular.json index 9dab8de8d..47afd9657 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "OctoPrintDash": { + "OctoDash": { "projectType": "application", "schematics": { "@schematics/angular:component": { @@ -18,6 +18,7 @@ "builder": "@angular-devkit/build-angular:browser", "options": { "aot": true, + "allowedCommonJsDependencies": ["angular-svg-round-progressbar"], "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", @@ -40,9 +41,7 @@ "output": "/" } ], - "styles": [ - "src/styles.scss" - ], + "styles": ["src/styles.scss"], "scripts": [] }, "configurations": { @@ -79,18 +78,18 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "OctoPrintDash:build" + "browserTarget": "OctoDash:build" }, "configurations": { "production": { - "browserTarget": "OctoPrintDash:build:production" + "browserTarget": "OctoDash:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "OctoPrintDash:build" + "browserTarget": "OctoDash:build" } }, "test": { @@ -100,47 +99,35 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets", - "src/assets/icon/icon-main-title.svg" - ], - "styles": [ - "src/styles.scss" - ], + "assets": ["src/favicon.ico", "src/assets", "src/assets/icon/icon-main-title.svg"], + "styles": ["src/styles.scss"], "scripts": [] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] + "tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "tsconfig.json"], + "exclude": ["**/node_modules/**"] } }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "OctoPrintDash:serve" + "devServerTarget": "OctoDash:serve" }, "configurations": { "production": { - "devServerTarget": "OctoPrintDash:serve:production" + "devServerTarget": "OctoDash:serve:production" } } } } } }, - "defaultProject": "OctoPrintDash", + "defaultProject": "OctoDash", "cli": { "analytics": "e1569209-2b82-4195-9c15-0669a78feccb" } -} \ No newline at end of file +} diff --git a/helper/discover.js b/helper/discover.js new file mode 100644 index 000000000..762080859 --- /dev/null +++ b/helper/discover.js @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable import/no-commonjs */ + +const mdns = require('mdns'); +const compareVersions = require('compare-versions'); + +const minimumVersion = '1.3.5'; +let mdnsBrowser; +let nodes = []; + +function discoverNodes(window) { + nodes = []; + mdnsBrowser = mdns.createBrowser(mdns.tcp('octoprint')); + mdnsBrowser.on('serviceUp', service => { + nodes.push({ + id: service.interfaceIndex, + name: service.name, + version: service.txtRecord.version, + url: `http://${service.host.replace(/\.$/, '')}:${service.port}${service.txtRecord.path}api/`, + disable: compareVersions(minimumVersion, service.txtRecord.version) === -1, + }); + sendNodes(window); + }); + + mdnsBrowser.on('serviceDown', service => { + nodes = nodes.filter(node => node.id !== service.interfaceIndex); + sendNodes(window); + }); + + mdnsBrowser.start(); +} + +function stopDiscovery() { + mdnsBrowser.stop(); +} + +function sendNodes(window) { + window.webContents.send('discoveredNodes', nodes); +} + +module.exports = { discoverNodes, stopDiscovery }; diff --git a/helper/listener.js b/helper/listener.js new file mode 100644 index 000000000..f16fd6684 --- /dev/null +++ b/helper/listener.js @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable import/no-commonjs */ + +const path = require('path'); +const url = require('url'); + +const exec = require('child_process').exec; + +const sendCustomStyles = require('./styles'); +const { downloadUpdate, sendVersionInfo } = require('./update'); +const { discoverNodes, stopDiscovery } = require('./discover'); + +function activateScreenSleepListener(ipcMain) { + ipcMain.on('screenSleep', () => { + exec('xset dpms force standby'); + }); + + ipcMain.on('screenWakeup', () => { + exec('xset s off'); + exec('xset -dpms'); + exec('xset s noblank'); + }); +} + +function activateReloadListener(ipcMain, window) { + ipcMain.on('reload', () => { + window.loadURL( + url.format({ + pathname: path.join(__dirname, 'dist/index.html'), + protocol: 'file:', + slashes: true, + }), + ); + }); +} + +function activateAppInfoListener(ipcMain, window, app) { + ipcMain.on('appInfo', () => { + sendCustomStyles(window); + sendVersionInfo(window, app); + }); +} + +function activateUpdateListener(ipcMain, window) { + ipcMain.on('update', (_, updateInfo) => { + downloadUpdate(updateInfo, window); + }); +} + +function activateDiscoverListener(ipcMain, window) { + ipcMain.on('discover', () => { + discoverNodes(window); + }); + + ipcMain.on('stopDiscover', () => { + stopDiscovery(); + }); +} + +function activateListeners(ipcMain, window, app) { + activateAppInfoListener(ipcMain, window, app); + activateScreenSleepListener(ipcMain); + activateReloadListener(ipcMain, window); + activateUpdateListener(ipcMain, window); + activateDiscoverListener(ipcMain, window); +} + +module.exports = activateListeners; diff --git a/helper/styles.js b/helper/styles.js new file mode 100644 index 000000000..4be31c09d --- /dev/null +++ b/helper/styles.js @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable import/no-commonjs */ + +const fs = require('fs'); +const path = require('path'); +const { app } = require('electron'); + +function sendCustomStyles(window) { + fs.readFile(path.join(app.getPath('userData'), 'custom-styles.css'), 'utf-8', (err, data) => { + if (err) { + if (err.code === 'ENOENT') { + fs.writeFile(path.join(app.getPath('userData'), 'custom-styles.css'), '', err => { + if (err) { + window.webContents.send('customStylesError', err); + } else { + window.webContents.send('customStyles', ''); + } + }); + } else { + window.webContents.send('customStylesError', err); + } + } else { + window.webContents.send('customStyles', data); + } + }); +} + +module.exports = sendCustomStyles; diff --git a/helper/update.js b/helper/update.js new file mode 100644 index 000000000..f3d5aa776 --- /dev/null +++ b/helper/update.js @@ -0,0 +1,103 @@ +/* eslint-disable no-sync */ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable import/no-commonjs */ + +const fs = require('fs'); +const got = require('got'); +const stream = require('stream'); +const { promisify } = require('util'); +const progress = require('progress-stream'); + +const exec = require('child_process').exec; + +function downloadUpdate(updateInfo, window) { + const downloadPath = '/tmp/octodash.deb'; + + exec('arch', (err, stdout, stderr) => { + if (err || stderr) { + window.webContents.send('updateError', { + error: err || { message: stderr }, + }); + } + got(updateInfo.assetsURL) + .then(releaseFiles => { + const reducer = (accumulator, currentValue) => accumulator + currentValue; + const averageETA = []; + let downloadURL; + let packageSize; + for (const package of JSON.parse(releaseFiles.body)) { + if (package.name.includes(stdout.trim())) { + downloadURL = package.browser_download_url; + packageSize = package.size; + } + } + if (downloadURL) { + const downloadPipeline = promisify(stream.pipeline); + const downloadProgress = progress({ + length: packageSize, + time: 300, + }); + + downloadProgress.on('progress', progress => { + averageETA.push(progress.eta); + if (averageETA.length > 4) averageETA.shift(); + window.webContents.send('updateDownloadProgress', { + percentage: progress.percentage, + transferred: (progress.transferred / 100000).toFixed(1), + total: (progress.length / 1000000).toFixed(1), + remaining: (progress.remaining / 100000).toFixed(1), + eta: new Date(averageETA.reduce(reducer) * 1000).toISOString().substr(14, 5), + runtime: new Date(progress.runtime * 1000).toISOString().substr(14, 5), + delta: (progress.delta / 100000).toFixed(1), + speed: (progress.speed / 1000000).toFixed(2), + }); + }); + + try { + if (fs.existsSync(downloadPath)) fs.unlinkSync(downloadPath); + } catch { + // no need to handle this properly + } + + downloadPipeline(got.stream(downloadURL), downloadProgress, fs.createWriteStream(downloadPath)) + .catch(error => { + window.webContents.send('updateError', { + error: { + message: `Can't download package! ${error.message}.`, + }, + }); + }) + .then(() => { + window.webContents.send('updateDownloadFinished'); + exec('sudo ~/scripts/update-octodash', (err, _, stderr) => { + if (err || stderr) { + window.webContents.send('updateError', { + error: err || { message: stderr }, + }); + } else { + window.webContents.send('updateInstalled'); + } + }); + }); + } else { + window.webContents.send('updateError', { + error: { + message: `Can't find matching package for architecture ${stdout}.`, + }, + }); + } + }) + .catch(error => { + error.message = `Can't load releases. ${error.message}`; + window.webContents.send('updateError', { error }); + }); + }); +} + +function sendVersionInfo(window, app) { + window.webContents.send('versionInformation', { + version: app.getVersion(), + }); +} + +module.exports = { downloadUpdate, sendVersionInfo }; diff --git a/main.js b/main.js index cee8ce0bc..7f9906bb4 100644 --- a/main.js +++ b/main.js @@ -1,34 +1,22 @@ -/* eslint-disable no-undef */ /* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable import/no-commonjs */ + const { app, BrowserWindow, ipcMain } = require('electron'); -const electronStore = require('electron-store'); -const fs = require('fs'); -const got = require('got'); const path = require('path'); const url = require('url'); -const stream = require('stream'); -const { promisify } = require('util'); -const progress = require('progress-stream'); - -const exec = require('child_process').exec; - -const store = new electronStore(); const args = process.argv.slice(1); const big = args.some(val => val === '--big'); const dev = args.some(val => val === '--serve'); +const activateListeners = require('./helper/listener'); + app.commandLine.appendSwitch('touch-events', 'enabled'); app.allowRendererProcessReuse = true; let window; function createWindow() { - config = store.get('config'); - store.onDidChange('config', newValue => { - config = newValue; - }); - const { screen, session } = require('electron'); if (!dev) { @@ -46,21 +34,21 @@ function createWindow() { const mainScreen = screen.getPrimaryDisplay(); window = new BrowserWindow({ - width: dev ? (big ? 1400 : 1080) : mainScreen.size.width, + width: dev ? (big ? 1360 : 1080) : mainScreen.size.width, height: dev ? (big ? 502 : 342) : mainScreen.size.height, - frame: dev ? true : false, + frame: dev, backgroundColor: '#353b48', webPreferences: { nodeIntegration: true, enableRemoteModule: true, + worldSafeExecuteJavaScript: true, + // TODO: enable + contextBridge (probably electron-ngx before release 12) + contextIsolation: false, }, icon: path.join(__dirname, 'src', 'assets', 'icon', 'icon.png'), }); if (dev) { - require('electron-reload')(__dirname, { - electron: require(`${__dirname}/node_modules/electron`), - }); window.loadURL('http://localhost:4200'); window.webContents.openDevTools(); } else { @@ -74,163 +62,13 @@ function createWindow() { window.setFullScreen(true); } - activateAppInfoListener(); - activateScreenSleepListener(); - activateReloadListener(); - activateUpdateListener(); + activateListeners(ipcMain, window, app); window.on('closed', () => { window = null; }); } -function activateScreenSleepListener() { - ipcMain.on('screenSleep', () => { - exec('xset dpms force standby'); - }); - - ipcMain.on('screenWakeup', () => { - exec('xset s off'); - exec('xset -dpms'); - exec('xset s noblank'); - }); -} - -function activateReloadListener() { - ipcMain.on('reload', () => { - window.loadURL( - url.format({ - pathname: path.join(__dirname, 'dist/index.html'), - protocol: 'file:', - slashes: true, - }), - ); - }); -} - -function activateAppInfoListener() { - ipcMain.on('appInfo', () => { - sendCustomStyles(); - sendVersionInfo(); - }); -} - -function activateUpdateListener() { - ipcMain.on('update', (_, updateInfo) => { - downloadUpdate(updateInfo); - }); -} - -function sendCustomStyles() { - fs.readFile(path.join(app.getPath('userData'), 'custom-styles.css'), 'utf-8', (err, data) => { - if (err) { - if (err.code === 'ENOENT') { - fs.writeFile(path.join(app.getPath('userData'), 'custom-styles.css'), '', err => { - if (err) { - window.webContents.send('customStylesError', err); - } else { - window.webContents.send('customStyles', ''); - } - }); - } else { - window.webContents.send('customStylesError', err); - } - } else { - window.webContents.send('customStyles', data); - } - }); -} - -function sendVersionInfo() { - window.webContents.send('versionInformation', { - version: app.getVersion(), - }); -} - -function downloadUpdate(updateInfo) { - const downloadPath = '/tmp/octodash.deb'; - - exec('arch', (err, stdout, stderr) => { - if (err || stderr) { - window.webContents.send('updateError', { - error: err ? err : { message: stderr }, - }); - } - got(updateInfo.assetsURL) - .then(releaseFiles => { - const reducer = (accumulator, currentValue) => accumulator + currentValue; - let averageETA = []; - let downloadURL; - let packageSize; - for (let package of JSON.parse(releaseFiles.body)) { - if (package.name.includes(stdout.trim())) { - downloadURL = package.browser_download_url; - packageSize = package.size; - } - } - if (downloadURL) { - const downloadPipeline = promisify(stream.pipeline); - let downloadProgress = progress({ - length: packageSize, - time: 300, - }); - - downloadProgress.on('progress', progress => { - averageETA.push(progress.eta); - if (averageETA.length > 4) averageETA.shift(); - window.webContents.send('updateDownloadProgress', { - percentage: progress.percentage, - transferred: (progress.transferred / 100000).toFixed(1), - total: (progress.length / 1000000).toFixed(1), - remaining: (progress.remaining / 100000).toFixed(1), - eta: new Date(averageETA.reduce(reducer) * 1000).toISOString().substr(14, 5), - runtime: new Date(progress.runtime * 1000).toISOString().substr(14, 5), - delta: (progress.delta / 100000).toFixed(1), - speed: (progress.speed / 1000000).toFixed(2), - }); - }); - - try { - if (fs.existsSync(downloadPath)) fs.unlinkSync(downloadPath); - } catch { - // no need to handle this properly - } - - downloadPipeline(got.stream(downloadURL), downloadProgress, fs.createWriteStream(downloadPath)) - .catch(error => { - window.webContents.send('updateError', { - error: { - message: `Can't download package! ${error.message}.`, - }, - }); - }) - .then(() => { - window.webContents.send('updateDownloadFinished'); - exec('sudo ~/scripts/update-octodash', (err, _, stderr) => { - if (err || stderr) { - window.webContents.send('updateError', { - error: err ? err : { message: stderr }, - }); - } else { - window.webContents.send('updateInstalled'); - } - }); - }); - } else { - window.webContents.send('updateError', { - error: { - message: `Can't find matching package for architecture ${stdout}.`, - }, - }); - } - }) - .catch(error => { - error.message = `Can't load releases. ${error.message}`; - window.webContents.send('updateError', { error }); - }); - }); -} - app.on('ready', createWindow); app.on('window-all-closed', () => { diff --git a/package-lock.json b/package-lock.json index 3ee168f91..23b82a064 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4685,6 +4685,11 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==" + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -6234,15 +6239,6 @@ } } }, - "electron-reload": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/electron-reload/-/electron-reload-1.5.0.tgz", - "integrity": "sha512-L9X6LzsL3Bt2j0eJ4/MBrI9Vt902KvVUtBB7J4qrL1A9sXqC2fE0lpvUAlOThpJYh6zWO1l86U/YiEN9bDURHw==", - "dev": true, - "requires": { - "chokidar": "^3.0.2" - } - }, "electron-store": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-6.0.0.tgz", @@ -9042,6 +9038,11 @@ "@hapi/topo": "^5.0.0" } }, + "jquery": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9628,6 +9629,22 @@ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "dev": true }, + "mdns": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/mdns/-/mdns-2.5.1.tgz", + "integrity": "sha512-JglS7Ed3Yf0BCpyC7LXA1MUrumMV8jj4g67nT3+m886SFYllz2HWBg8ObywFXWbBSv5gW0meMOOS4vVa2jZGCw==", + "requires": { + "bindings": "~1.2.1", + "nan": "^2.14.0" + }, + "dependencies": { + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -10001,9 +10018,7 @@ "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "dev": true, - "optional": true + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" }, "nanomatch": { "version": "1.2.13", diff --git a/package.json b/package.json index 55d1b6852..8517e4a59 100644 --- a/package.json +++ b/package.json @@ -79,9 +79,13 @@ "@fortawesome/free-solid-svg-icons": "^5.14.0", "ajv": "^6.12.5", "angular-svg-round-progressbar": "^4.0.0", + "compare-versions": "^3.6.0", "electron-store": "^6.0.0", "got": "^11.6.2", + "jquery": "^3.5.1", + "got": "^11.6.2", "lodash": "^4.17.20", + "mdns": "^2.5.1", "ngx-spinner": "^10.0.1", "progress-stream": "^2.0.0", "rxjs": "~6.6.3", @@ -101,7 +105,6 @@ "codelyzer": "^6.0.0", "electron": "^10.1.2", "electron-builder": "^22.8.0", - "electron-reload": "^1.5.0", "eslint": "7.9.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-prettier": "^3.1.4", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5b9b09aa0..30f674503 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,9 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, NgZone, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import _ from 'lodash'; import { AppService } from './app.service'; import { ConfigService } from './config/config.service'; +import { NotificationService } from './notification/notification.service'; +import { OctoprintScriptService } from './octoprint-script.service'; declare global { interface Window { @@ -19,15 +21,35 @@ declare global { templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class AppComponent { - public constructor(private service: AppService, private configService: ConfigService, private router: Router) { +export class AppComponent implements OnInit { + public constructor( + private service: AppService, + private configService: ConfigService, + private octoprintScriptService: OctoprintScriptService, + private notificationService: NotificationService, + private router: Router, + private zone: NgZone, + ) {} + + public ngOnInit(): void { this.initialize(); } - private initialize(): void { + private async initialize(): Promise { if (this.configService && this.configService.isInitialized()) { if (this.configService.isLoaded()) { if (this.configService.isValid()) { + try { + await this.zone.run(async () => { + await this.octoprintScriptService.initialize(this.configService.getURL('')); + this.octoprintScriptService.authenticate(this.configService.getAccessKey()); + }); + } catch { + this.notificationService.setError( + "Can't get OctoPrint script!", + 'Please restart your machine. If the error persists open a new issue on GitHub.', + ); + } if (this.configService.isTouchscreen()) { this.router.navigate(['/main-screen']); } else { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b7657a9bf..ca825b1b4 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -22,11 +22,13 @@ import { FilesComponent } from './files/files.component'; import { JobStatusComponent } from './job-status/job-status.component'; import { JobService } from './job.service'; import { LayerProgressComponent } from './layer-progress/layer-progress.component'; +import { LongPress } from './long-press.directive'; import { MainMenuComponent } from './main-menu/main-menu.component'; import { MainScreenComponent } from './main-screen/main-screen.component'; import { MainScreenNoTouchComponent } from './main-screen/no-touch/main-screen-no-touch.component'; import { NotificationComponent } from './notification/notification.component'; import { NotificationService } from './notification/notification.service'; +import { OctoprintScriptService } from './octoprint-script.service'; import { PrintControlComponent } from './print-control/print-control.component'; import { PrinterStatusComponent } from './printer-status/printer-status.component'; import { PrinterService } from './printer.service'; @@ -39,23 +41,24 @@ import { URLSafePipe } from './url.pipe'; declarations: [ AppComponent, BottomBarComponent, - PrinterStatusComponent, + ControlComponent, + FilamentComponent, + FilesComponent, + InvalidConfigComponent, JobStatusComponent, LayerProgressComponent, - InvalidConfigComponent, - NoConfigComponent, - PrintControlComponent, - NotificationComponent, + LongPress, MainMenuComponent, - ControlComponent, MainScreenComponent, MainScreenNoTouchComponent, - FilamentComponent, - FilesComponent, + NoConfigComponent, + NotificationComponent, + PrintControlComponent, + PrinterStatusComponent, SettingsComponent, - URLSafePipe, StandbyComponent, UpdateComponent, + URLSafePipe, ], imports: [ BrowserModule, @@ -68,7 +71,7 @@ import { URLSafePipe } from './url.pipe'; BrowserAnimationsModule, MatRippleModule, ], - providers: [AppService, ConfigService, NotificationService, PrinterService, JobService], + providers: [AppService, ConfigService, NotificationService, PrinterService, JobService, OctoprintScriptService], bootstrap: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], }) diff --git a/src/app/config/config.service.ts b/src/app/config/config.service.ts index b2d971a29..d62876369 100644 --- a/src/app/config/config.service.ts +++ b/src/app/config/config.service.ts @@ -254,6 +254,10 @@ export class ConfigService { public showThumbnailByDefault(): boolean { return this.config.octodash.preferPreviewWhilePrinting; } + + public getAccessKey(): string { + return this.config.octoprint.accessToken; + } } interface HttpHeader { @@ -427,11 +431,11 @@ const schema = { }, feedSpeed: { $id: '#/properties/filament/properties/feedSpeed', - type: 'integer', + type: 'number', }, feedSpeedSlow: { $id: '#/properties/filament/properties/feedSpeedSlow', - type: 'integer', + type: 'number', }, purgeDistance: { $id: '#/properties/filament/properties/purgeDistance', diff --git a/src/app/config/no-config/no-config.component.html b/src/app/config/no-config/no-config.component.html index 3033ffaba..ae57fd5cf 100644 --- a/src/app/config/no-config/no-config.component.html +++ b/src/app/config/no-config/no-config.component.html @@ -1,23 +1,26 @@
- - - -
- - back + +
+ + back +
- next - + +
+ next + +
+
Hey there! @@ -41,9 +44,88 @@
+
- First, tell me some facts about your printer so I can personalize OctoDash for you. + First things first: Please select your OctoPrint instance from the list below or enter the IP/URL manually. + +
+
+ +
+ {{ node.name }} +
+ + Version {{ node.version }}, URL: {{ node.url.replace('/api/', '') }} + +
+ +
+ searching ... +
+
enter manually
+
+
+
+ http:// + + : + +

+ Port 5000 for vanilla OctoPrint, Port 80 for OctoPi +

+
+
+ search again +
+
+
+ +
+ + Please authenticate me now. You can either click the button below and confirm the request in your OctoPrint + webinterface or enter an API Key manually. + + +
+ + +
+
+ +
+ + Now tell me some facts about your printer so I can personalize OctoDash for you.
@@ -63,82 +145,98 @@ Wiki.
-
+
I also need some information about your extruder. - - mm +
+ + - + + + + + + + mm +

- - mm/s +
+ + - + + + + + + + mm/s +
+
- These values will be used during the Filament Change Process. Be sure to have your Feed Length (length between - Extruder and Hotend) and Feed Speed configured correctly. + These values will be used during the Filament Change Process. Make sure to have your Feed Length (distance + Extruder to Hotend) and Feed Speed configured correctly.
-
- - Now I need to know something about your OctoPrint setup, so I can talk to your printer. - -
- - http:// - - : - -
- - -
-
-
+
And now personalize me to your liking.
@@ -148,6 +246,8 @@ Use Touchscreen

+ +
-
+
What plugins are you running?
@@ -227,24 +327,20 @@
-
+
Great! I'll check everything.
+ + + Octoprint Connection + - - Config Validation + + Validating Config {{ error }}
- - - - Octoprint Connection - - {{ octoprintConnectionError }} -
-
@@ -252,13 +348,7 @@ {{ configSaved }}
- - done - + done
diff --git a/src/app/config/no-config/no-config.component.scss b/src/app/config/no-config/no-config.component.scss index 08440bf85..dca8bfcd1 100644 --- a/src/app/config/no-config/no-config.component.scss +++ b/src/app/config/no-config/no-config.component.scss @@ -30,11 +30,41 @@ &__input { background: transparent; - margin-top: 9vh; - padding: 1.5vw 3vh; + margin-top: 7vh; + padding: 3vh 2.5vw; border: 2px solid #dcdde1; border-radius: 2.5vw; width: 55vw; + line-height: 125%; + + &__numeric { + border-radius: 0; + + &-wrapper { + display: inline-table; + text-align: center; + } + + &-adjust { + display: inline-flex; + padding: 3vh 3vw; + border: 2px solid #dcdde1; + border-radius: 2.5vw 0 0 2.5vw; + margin-right: -2px; + font-size: 4.3vw; + line-height: 125%; + + &-increase { + border-radius: 0 2.5vw 2.5vw 0; + margin-left: -2px; + } + } + + &:focus { + border: 2px solid #dcdde1 !important; + border-bottom: 2px solid #44bd32 !important; + } + } &:focus { outline: 0; @@ -47,7 +77,9 @@ &-suffix { font-size: 3vw; - padding-left: 1vw; + display: inline-block; + opacity: 0.6; + margin-right: 10vw; } } @@ -117,6 +149,61 @@ text-align: center; } + &__discovered-instances { + &__wrapper { + height: 40vh; + padding: 4vh 3vw; + margin-right: 2vw; + overflow-y: auto; + overflow-x: hidden; + } + + &__searching { + display: block; + text-align: center; + margin-top: 10vh; + font-size: 0.8rem; + } + + &__node { + display: block; + background-color: #718093; + border-radius: 1vw; + margin-bottom: 2vh; + padding: 2vh 2vw; + + &-name { + white-space: nowrap; + text-overflow: ellipsis; + font-size: 0.8rem; + float: left; + width: 90%; + } + + &-details { + font-size: 0.55rem; + opacity: 0.7; + } + + &-connect-icon { + height: 10vh; + float: right; + margin-top: 1.8vh; + } + } + + &__manual { + text-align: center; + font-size: 0.7rem; + line-height: 14vh; + } + } + + &__login-request-button-wrapper { + text-align: center; + padding: 8vh 0 0; + } + &__1 { &-welcome { display: block; diff --git a/src/app/config/no-config/no-config.component.ts b/src/app/config/no-config/no-config.component.ts index 523f4157f..d92649068 100644 --- a/src/app/config/no-config/no-config.component.ts +++ b/src/app/config/no-config/no-config.component.ts @@ -1,6 +1,8 @@ -import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { NotificationService } from 'src/app/notification/notification.service'; +import { OctoprintScriptService } from 'src/app/octoprint-script.service'; import { Config, ConfigService } from '../config.service'; @@ -10,8 +12,11 @@ import { Config, ConfigService } from '../config.service'; styleUrls: ['./no-config.component.scss'], }) export class NoConfigComponent implements OnInit { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private ipc: any; + public page = 0; - public totalPages = 6; + public totalPages = 7; public configUpdate: boolean; public config: Config; @@ -19,159 +24,168 @@ export class NoConfigComponent implements OnInit { public configValid: boolean; public configSaved: string; - public octoprintConnection: boolean; + public manualURL = false; + public octoprintNodes: OctoprintNodes; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private OctoPrint: any; + + public octoprintConnection = false; public octoprintConnectionError: string; - public constructor(private configService: ConfigService, private http: HttpClient, private router: Router) { + public constructor( + private configService: ConfigService, + private http: HttpClient, + private router: Router, + private notificationService: NotificationService, + private octoprintScriptService: OctoprintScriptService, + private changeDetector: ChangeDetectorRef, + private zone: NgZone, + ) { + try { + this.ipc = window.require('electron').ipcRenderer; + } catch (e) { + this.notificationService.setError( + "Can't connect to backend", + 'Please restart your system. If the issue persists open an issue on GitHub.', + ); + } + this.configUpdate = this.configService.isUpdate(); - console.log(this.configUpdate); if (this.configUpdate) { this.config = configService.getCurrentConfig(); } else { - this.config = { - octoprint: { - url: 'http://localhost:5000/api/', - accessToken: '', - }, - printer: { - name: '', - xySpeed: 150, - zSpeed: 5, - defaultTemperatureFanSpeed: { - hotend: 200, - heatbed: 60, - fan: 100, - }, - }, - filament: { - thickness: 1.75, - density: 1.25, - feedLength: 0, - feedSpeed: 30, - feedSpeedSlow: 3, - purgeDistance: 30, - useM600: false, - }, - plugins: { - displayLayerProgress: { - enabled: true, - }, - enclosure: { - enabled: false, - ambientSensorID: null, - filament1SensorID: null, - filament2SensorID: null, - }, - filamentManager: { - enabled: true, - }, - preheatButton: { - enabled: true, - }, - printTimeGenius: { - enabled: true, - }, - psuControl: { - enabled: false, - turnOnPSUWhenExitingSleep: false, - }, - }, - octodash: { - customActions: [ - { - icon: 'home', - command: 'G28', - color: '#dcdde1', - confirm: false, - exit: true, - }, - { - icon: 'ruler-vertical', - command: 'G29', - color: '#44bd32', - confirm: false, - exit: true, - }, - { - icon: 'fire-alt', - command: 'M140 S50; M104 S185', - color: '#e1b12c', - confirm: false, - exit: true, - }, - { - icon: 'snowflake', - command: 'M140 S0; M104 S0', - color: '#0097e6', - confirm: false, - exit: true, - }, - { - icon: 'redo-alt', - command: '[!RELOAD]', - color: '#7f8fa6', - confirm: true, - exit: false, - }, - { - icon: 'skull', - command: '[!KILL]', - color: '#e84118', - confirm: true, - exit: false, - }, - ], - fileSorting: { - attribute: 'name', - order: 'asc', - }, - pollingInterval: 2000, - touchscreen: true, - turnScreenOffWhileSleeping: false, - preferPreviewWhilePrinting: false, - }, - }; + this.config = this.getDefaultConfig(); } this.config = this.configService.revertConfigForInput(this.config); } public ngOnInit(): void { this.changeProgress(); + + this.ipc.on('discoveredNodes', (_, nodes: OctoprintNodes) => { + this.octoprintNodes = nodes; + this.changeDetector.detectChanges(); + }); + } + + public discoverOctoprintInstances(): void { + this.octoprintNodes = null; + this.ipc.send('discover'); + setTimeout(() => { + const searching = document.querySelector('.no-config__discovered-instances__searching'); + if (searching) { + searching.innerHTML = 'no instances found.'; + } + }, 10000); } - public testOctoprintAPI(): boolean { - const httpHeaders = { - headers: new HttpHeaders({ - 'x-api-key': this.config.octoprint.accessToken, - }), + public setOctoprintInstance(node: OctoprintNodes): void { + this.config.octoprint.url = node.url; + this.config = this.configService.revertConfigForInput(this.config); + this.increasePage(); + } + + public enterURLManually(): void { + this.config.octoprint.urlSplit = { + url: 'localhost', + port: 5000, }; - this.http.get(this.config.octoprint.url + 'connection', httpHeaders).subscribe( - (): void => { - this.octoprintConnection = true; - this.saveConfig(); - }, - (error: HttpErrorResponse): void => { - this.octoprintConnection = false; - this.octoprintConnectionError = error.message; - }, + this.manualURL = true; + } + + private async loadOctoprintClient() { + try { + await this.zone.run(async () => { + await this.octoprintScriptService.initialize( + `http://${this.config.octoprint.urlSplit.url}:${this.config.octoprint.urlSplit.port}/api/`, + ); + this.OctoPrint = this.octoprintScriptService.getInstance(); + }); + } catch (e) { + this.notificationService.setError( + "Can't connect to OctoPrint!", + `Check the URL/IP and make sure that your firewall allows access to port ${this.config.octoprint.urlSplit.port} on host ${this.config.octoprint.urlSplit.url}.`, + ); + this.page = 1; + } + } + + public loginWithOctoPrintUI(): void { + this.notificationService.setUpdate( + 'Login request send!', + 'Please confirm the request via the popup in the OctoPrint WebUI.', ); - return true; + + const closeInfo = setTimeout(() => { + this.notificationService.closeNotification(); + }, 3000); + + this.OctoPrint.plugins.appkeys + .authenticate('OctoDash') + .done((apiKey: string) => { + this.config.octoprint.accessToken = apiKey; + this.octoprintScriptService.authenticate(apiKey); + this.OctoPrint = this.octoprintScriptService.getInstance(); + + // FIXME: to be removed before merge + this.OctoPrint.printerprofiles + .list() + .done(profiles => { + this.config.printer.name = profiles.profiles._default.name; + }) + .fail(() => console.error('ERR')); + // END + + this.changeDetector.detectChanges(); + setTimeout(() => { + this.increasePage(); + }, 600); + }) + .fail(() => { + this.notificationService.setWarning( + 'Something went wrong!', + "Can' retrieve the API Key, please try again or create one manually and enter it down below.", + ); + }) + .always(() => { + clearTimeout(closeInfo); + }); } - public createConfig(): boolean { + changeFeedLength(amount: number): void { + if (this.config.filament.feedLength + amount < 0) { + this.config.filament.feedLength = 0; + } else if (this.config.filament.feedLength + amount > 9999) { + this.config.filament.feedLength = 9999; + } else { + this.config.filament.feedLength += amount; + } + } + + changeFeedSpeed(amount: number): void { + if (this.config.filament.feedSpeed + amount < 0) { + this.config.filament.feedSpeed = 0; + } else if (this.config.filament.feedSpeed + amount > 999) { + this.config.filament.feedSpeed = 999; + } else { + this.config.filament.feedSpeed += amount; + } + } + + public createConfig(): void { this.configErrors = []; this.octoprintConnectionError = null; this.config = this.configService.createConfigFromInput(this.config); this.validateConfig(); - return true; } - public validateConfig(): void { + public async validateConfig(): Promise { this.configValid = this.configService.validateGiven(this.config); if (!this.configValid) { this.configErrors = this.configService.getErrors(); } else { - this.testOctoprintAPI(); + this.saveConfig(); } } @@ -184,23 +198,167 @@ export class NoConfigComponent implements OnInit { this.router.navigate(['/main-screen']); } - public increasePage(): void { - this.page += 1; - if (this.page === this.totalPages) { - this.createConfig(); + private changePage(value: number): void { + if (this.page + value > this.totalPages || this.page + value < 0) { + return; } + this.beforeNavigation(value); + this.page = this.page + value; + this.afterNavigation(); this.changeProgress(); } - public decreasePage(): void { - if (this.page === 5) { - this.config = this.configService.revertConfigForInput(this.config); + private beforeNavigation(value: number): void { + switch (this.page) { + case 1: + this.ipc.send('stopDiscover'); + if (value > 0) { + this.loadOctoprintClient(); + } + break; + case this.totalPages - 1: + if (value < 0) { + this.config = this.configService.revertConfigForInput(this.config); + } + break; } - this.page -= 1; - this.changeProgress(); + } + + private afterNavigation(): void { + switch (this.page) { + case 1: + this.discoverOctoprintInstances(); + break; + case this.totalPages: + this.createConfig(); + break; + } + } + + public increasePage(): void { + this.changePage(1); + } + + public decreasePage(): void { + this.changePage(-1); } public changeProgress(): void { document.getElementById('progressBar').style.width = this.page * (20 / this.totalPages) + 'vw'; + this.changeDetector.detectChanges(); } + + public getDefaultConfig(): Config { + return { + octoprint: { + url: 'http://localhost:5000/api/', + accessToken: '', + }, + printer: { + name: '', + xySpeed: 150, + zSpeed: 5, + defaultTemperatureFanSpeed: { + hotend: 200, + heatbed: 60, + fan: 100, + }, + }, + filament: { + thickness: 1.75, + density: 1.25, + feedLength: 0, + feedSpeed: 20, + feedSpeedSlow: 3, + purgeDistance: 30, + useM600: false, + }, + plugins: { + displayLayerProgress: { + enabled: true, + }, + enclosure: { + enabled: false, + ambientSensorID: null, + filament1SensorID: null, + filament2SensorID: null, + }, + filamentManager: { + enabled: true, + }, + preheatButton: { + enabled: true, + }, + printTimeGenius: { + enabled: true, + }, + psuControl: { + enabled: false, + turnOnPSUWhenExitingSleep: false, + }, + }, + octodash: { + customActions: [ + { + icon: 'home', + command: 'G28', + color: '#dcdde1', + confirm: false, + exit: true, + }, + { + icon: 'ruler-vertical', + command: 'G29', + color: '#44bd32', + confirm: false, + exit: true, + }, + { + icon: 'fire-alt', + command: 'M140 S50; M104 S185', + color: '#e1b12c', + confirm: false, + exit: true, + }, + { + icon: 'snowflake', + command: 'M140 S0; M104 S0', + color: '#0097e6', + confirm: false, + exit: true, + }, + { + icon: 'redo-alt', + command: '[!RELOAD]', + color: '#7f8fa6', + confirm: true, + exit: false, + }, + { + icon: 'skull', + command: '[!KILL]', + color: '#e84118', + confirm: true, + exit: false, + }, + ], + fileSorting: { + attribute: 'name', + order: 'asc', + }, + pollingInterval: 2000, + touchscreen: true, + turnScreenOffWhileSleeping: false, + preferPreviewWhilePrinting: false, + }, + }; + } +} + +interface OctoprintNodes { + id: number; + name: string; + version: string; + url: string; + disable: boolean; } diff --git a/src/app/long-press.directive.ts b/src/app/long-press.directive.ts new file mode 100644 index 000000000..063efaeb9 --- /dev/null +++ b/src/app/long-press.directive.ts @@ -0,0 +1,50 @@ +import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; + +@Directive({ + selector: '[long-press]', +}) +export class LongPress { + pressing: boolean; + longPressing: boolean; + timeout: number; + interval: number; + + @Input() duration = 500; + @Input() frequency = 100; + + @Output() + onShortPress = new EventEmitter(); + + @Output() + onLongPress = new EventEmitter(); + + @Output() + onLongPressing = new EventEmitter(); + + @HostListener('touchstart', ['$event']) + @HostListener('mousedown', ['$event']) + onMouseDown(event: EventSource): void { + this.pressing = true; + this.longPressing = false; + this.timeout = setTimeout(() => { + this.longPressing = true; + this.onLongPress.emit(event); + this.interval = setInterval(() => { + this.onLongPressing.emit(event); + }, this.frequency); + }, this.duration); + } + + @HostListener('touchend', ['$event']) + @HostListener('mouseup', ['$event']) + @HostListener('mouseleave', ['$event']) + endPress(event: EventSource): void { + clearTimeout(this.timeout); + clearInterval(this.interval); + if (!this.longPressing && this.pressing) { + this.onShortPress.emit(event); + } + this.longPressing = false; + this.pressing = false; + } +} diff --git a/src/app/notification/notification.component.ts b/src/app/notification/notification.component.ts index e7d6e77cd..8c8f84ffd 100644 --- a/src/app/notification/notification.component.ts +++ b/src/app/notification/notification.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy } from '@angular/core'; +import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { Notification, NotificationService } from './notification.service'; @@ -18,7 +18,7 @@ export class NotificationComponent implements OnDestroy { }; public show = false; - public constructor(private notificationService: NotificationService) { + public constructor(private notificationService: NotificationService, private changeDetector: ChangeDetectorRef) { this.subscriptions.add( this.notificationService .getObservable() @@ -37,6 +37,7 @@ export class NotificationComponent implements OnDestroy { this.notification = notification; this.show = true; } + this.changeDetector.detectChanges(); } public ngOnDestroy(): void { diff --git a/src/app/notification/notification.service.ts b/src/app/notification/notification.service.ts index d7fa69af8..a103452b6 100644 --- a/src/app/notification/notification.service.ts +++ b/src/app/notification/notification.service.ts @@ -32,6 +32,10 @@ export class NotificationService { this.observer.next('close'); } + public closeNotification(): void { + this.observer.next('close'); + } + public setError(heading: string, text: string): void { if ((!this.hideNotifications && !this.bootGrace) || (this.bootGrace && !text.endsWith('0 Unknown Error'))) { if (this.observer) { diff --git a/src/app/octoprint-script.service.ts b/src/app/octoprint-script.service.ts new file mode 100644 index 000000000..84d31feec --- /dev/null +++ b/src/app/octoprint-script.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const OctoPrint: any; + +@Injectable() +export class OctoprintScriptService { + public loaded = false; + private octoprintURL: string; + + async initialize(octoprintURL: string): Promise { + this.octoprintURL = octoprintURL.replace('api/', ''); + const octoprintStaticURL = octoprintURL.replace('/api/', '/static/'); + const scripts: string[] = [`${octoprintStaticURL}webassets/packed_client.js`]; + await this.load(scripts); + OctoPrint.options.baseurl = this.octoprintURL; + } + + private load(scripts: string[]): Promise { + const promises: Promise[] = []; + scripts.forEach(script => promises.push(this.loadScript(script))); + return Promise.all(promises); + } + + private loadScript(src: string): Promise { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = src; + script.onload = () => resolve(); + script.onerror = () => reject(); + document.getElementsByTagName('head')[0].appendChild(script); + }); + } + + public authenticate(accessToken: string): void { + OctoPrint.options.apikey = accessToken; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public getInstance(): any { + return OctoPrint; + } +} diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 0101548a4..1514202bd 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -650,6 +650,7 @@
  • "cubes" by artworkbean from thenounproject.com
  • "Sort" by Adrien Coquet from thenounproject.com
  • "height" by Ralf Schmitzer from thenounproject.com
  • +
  • "connect" by Sebastian Belalcazar Lareo from thenounproject.com
  • Special Thanks

    diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss index f35c85338..0ae315826 100644 --- a/src/app/settings/settings.component.scss +++ b/src/app/settings/settings.component.scss @@ -211,7 +211,7 @@ &__save { display: block; text-align: center; - margin: 2.5vh 0 4vh; + margin: 4vh 0; } &__scroll { diff --git a/src/assets/connect.svg b/src/assets/connect.svg new file mode 100644 index 000000000..77bd16db7 --- /dev/null +++ b/src/assets/connect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/index.html b/src/index.html index 7d0b73096..3c1c8de4f 100644 --- a/src/index.html +++ b/src/index.html @@ -12,6 +12,10 @@ rel="stylesheet" /> + diff --git a/src/styles.scss b/src/styles.scss index ebfa469ba..ce02b0f96 100755 --- a/src/styles.scss +++ b/src/styles.scss @@ -4,11 +4,11 @@ @include mat-core(); // TODO: needed for ripple effect, need to check why -$OctoPrintDash-primary: mat-palette($mat-indigo); -$OctoPrintDash-accent: mat-palette($mat-pink, A200, A100, A400); -$OctoPrintDash-warn: mat-palette($mat-red); -$OctoPrintDash-theme: mat-dark-theme($OctoPrintDash-primary, $OctoPrintDash-accent, $OctoPrintDash-warn); -@include angular-material-theme($OctoPrintDash-theme); +$OctoDash-primary: mat-palette($mat-indigo); +$OctoDash-accent: mat-palette($mat-pink, A200, A100, A400); +$OctoDash-warn: mat-palette($mat-red); +$OctoDash-theme: mat-dark-theme($OctoDash-primary, $OctoDash-accent, $OctoDash-warn); +@include angular-material-theme($OctoDash-theme); *:not(path):not(svg) { font-family: 'Montserrat', sans-serif; diff --git a/tsconfig.json b/tsconfig.json index ae257c4d3..15a604cb1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,20 @@ { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist", - "sourceMap": true, - "declaration": false, - "module": "esnext", - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "importHelpers": true, - "target": "ESNext", - "typeRoots": [ - "node_modules/@types" - ], - "types": [], - "lib": ["es2018", "dom"], - "esModuleInterop": true - }, - "exclude": [ - "**/node_modules/**" - ] + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "sourceMap": true, + "declaration": false, + "module": "ESNEXT", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2015", + "typeRoots": ["node_modules/@types"], + "types": [], + "lib": ["ESNEXT", "dom"], + "esModuleInterop": true + }, + "exclude": ["**/node_modules/**"] }