From b4e703674b201e7f57646bd4218447165293156e Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Mon, 5 Apr 2021 12:32:37 +0200 Subject: [PATCH] settings ui updates --- terminus-core/src/api/index.ts | 1 + .../src/components/appRoot.component.pug | 2 +- .../src/components/appRoot.component.ts | 24 +- .../src/components/tabHeader.component.pug | 5 +- .../src/components/tabHeader.component.scss | 2 +- terminus-core/src/services/updater.service.ts | 58 +++- .../hotkeySettingsTab.component.pug | 23 ++ .../hotkeySettingsTab.component.scss | 7 + .../components/hotkeySettingsTab.component.ts | 57 ++++ .../src/components/settingsTab.component.pug | 323 ++---------------- .../src/components/settingsTab.component.scss | 11 +- .../src/components/settingsTab.component.ts | 56 +-- .../windowSettingsTab.component.pug | 279 +++++++++++++++ .../components/windowSettingsTab.component.ts | 49 +++ terminus-settings/src/index.ts | 10 + terminus-settings/src/settings.ts | 29 ++ .../appearanceSettingsTab.component.pug | 41 +-- .../appearanceSettingsTab.component.scss | 5 + .../terminalSettingsTab.component.pug | 15 +- 19 files changed, 580 insertions(+), 417 deletions(-) create mode 100644 terminus-settings/src/components/hotkeySettingsTab.component.pug create mode 100644 terminus-settings/src/components/hotkeySettingsTab.component.scss create mode 100644 terminus-settings/src/components/hotkeySettingsTab.component.ts create mode 100644 terminus-settings/src/components/windowSettingsTab.component.pug create mode 100644 terminus-settings/src/components/windowSettingsTab.component.ts create mode 100644 terminus-settings/src/settings.ts diff --git a/terminus-core/src/api/index.ts b/terminus-core/src/api/index.ts index 64824164..47b8acac 100644 --- a/terminus-core/src/api/index.ts +++ b/terminus-core/src/api/index.ts @@ -21,4 +21,5 @@ export { NotificationsService } from '../services/notifications.service' export { ShellIntegrationService } from '../services/shellIntegration.service' export { ThemesService } from '../services/themes.service' export { TabsService } from '../services/tabs.service' +export { UpdaterService } from '../services/updater.service' export * from '../utils' diff --git a/terminus-core/src/components/appRoot.component.pug b/terminus-core/src/components/appRoot.component.pug index 8d647885..81d76ef0 100644 --- a/terminus-core/src/components/appRoot.component.pug +++ b/terminus-core/src/components/appRoot.component.pug @@ -86,7 +86,7 @@ title-bar( button.btn.btn-secondary.btn-tab-bar.btn-update( *ngIf='updatesAvailable', title='Update available - Click to install', - (click)='updateApp()', + (click)='updater.update()', [fastHtmlBind]='updateIcon' ) diff --git a/terminus-core/src/components/appRoot.component.ts b/terminus-core/src/components/appRoot.component.ts index 9d64d1bf..b8c52e18 100644 --- a/terminus-core/src/components/appRoot.component.ts +++ b/terminus-core/src/components/appRoot.component.ts @@ -136,9 +136,13 @@ export class AppRootComponent { ngbModal.open(SafeModeModalComponent) } - this.updater.check().then(available => { - this.updatesAvailable = available - }) + setInterval(() => { + if (this.config.store.enableAutomaticUpdates) { + this.updater.check().then(available => { + this.updatesAvailable = available + }) + } + }, 3600 * 12) this.touchbar.update() @@ -190,20 +194,6 @@ export class AppRootComponent { return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right' } - async updateApp () { - if ((await this.electron.showMessageBox( - this.hostApp.getWindow(), - { - type: 'warning', - message: 'Installing the update will close all tabs and restart Terminus.', - buttons: ['Cancel', 'Update'], - defaultId: 1, - } - )).response === 1) { - this.updater.update() - } - } - onTabDragStart () { this.tabsDragging = true } diff --git a/terminus-core/src/components/tabHeader.component.pug b/terminus-core/src/components/tabHeader.component.pug index 76447550..4e335f65 100644 --- a/terminus-core/src/components/tabHeader.component.pug +++ b/terminus-core/src/components/tabHeader.component.pug @@ -3,5 +3,8 @@ #handle, [style.background-color]='tab.color', ) {{index + 1}} -.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}} +.name( + [title]='tab.customTitle || tab.title', + [class.no-hover]='config.store.terminal.hideCloseButton' +) {{tab.customTitle || tab.title}} button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') × diff --git a/terminus-core/src/components/tabHeader.component.scss b/terminus-core/src/components/tabHeader.component.scss index 7ec8a303..62e14145 100644 --- a/terminus-core/src/components/tabHeader.component.scss +++ b/terminus-core/src/components/tabHeader.component.scss @@ -76,7 +76,7 @@ $tabs-height: 38px; } } - &:hover .name { + &:hover .name:not(.no-hover) { -webkit-mask-image: linear-gradient(black 0 0), linear-gradient(to left, transparent 0%, black 100%); -webkit-mask-size: calc(100% - 60px) auto, 60px auto; -webkit-mask-repeat: no-repeat; diff --git a/terminus-core/src/services/updater.service.ts b/terminus-core/src/services/updater.service.ts index f60c8461..20bd28ef 100644 --- a/terminus-core/src/services/updater.service.ts +++ b/terminus-core/src/services/updater.service.ts @@ -4,6 +4,7 @@ import { Injectable } from '@angular/core' import { Logger, LogService } from './log.service' import { ElectronService } from './electron.service' import { ConfigService } from './config.service' +import { HostAppService } from './hostApp.service' const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest' @@ -17,8 +18,9 @@ export class UpdaterService { private constructor ( log: LogService, + config: ConfigService, private electron: ElectronService, - private config: ConfigService, + private hostApp: HostAppService, ) { this.logger = log.create('updater') @@ -58,10 +60,42 @@ export class UpdaterService { } async check (): Promise { - if (!this.config.store.enableAutomaticUpdates) { - return false - } - if (!this.electronUpdaterAvailable) { + if (this.electronUpdaterAvailable) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/init-declarations, prefer-const + let cancel + const onNoUpdate = () => { + cancel() + resolve(false) + } + const onUpdate = () => { + cancel() + resolve(this.downloaded) + } + const onError = (err) => { + cancel() + reject(err) + } + cancel = () => { + this.electron.autoUpdater.off('error', onError) + this.electron.autoUpdater.off('update-not-available', onNoUpdate) + this.electron.autoUpdater.off('update-available', onUpdate) + } + this.electron.autoUpdater.on('error', onError) + this.electron.autoUpdater.on('update-not-available', onNoUpdate) + this.electron.autoUpdater.on('update-available', onUpdate) + this.electron.autoUpdater.checkForUpdates() + }) + + this.electron.autoUpdater.on('update-available', () => { + this.logger.info('Update available') + }) + + this.electron.autoUpdater.once('update-not-available', () => { + this.logger.info('No updates') + }) + + } else { this.logger.debug('Checking for updates through fallback method.') const response = await axios.get(UPDATES_URL) const data = response.data @@ -81,8 +115,18 @@ export class UpdaterService { if (!this.electronUpdaterAvailable) { this.electron.shell.openExternal(this.updateURL) } else { - await this.downloaded - this.electron.autoUpdater.quitAndInstall() + if ((await this.electron.showMessageBox( + this.hostApp.getWindow(), + { + type: 'warning', + message: 'Installing the update will close all tabs and restart Terminus.', + buttons: ['Cancel', 'Update'], + defaultId: 1, + } + )).response === 1) { + await this.downloaded + this.electron.autoUpdater.quitAndInstall() + } } } } diff --git a/terminus-settings/src/components/hotkeySettingsTab.component.pug b/terminus-settings/src/components/hotkeySettingsTab.component.pug new file mode 100644 index 00000000..4d8d93cd --- /dev/null +++ b/terminus-settings/src/components/hotkeySettingsTab.component.pug @@ -0,0 +1,23 @@ +h3.mb-3 Hotkeys + +.input-group.mb-4 + .input-group-prepend + .input-group-text + i.fas.fa-fw.fa-search + input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter') + +.form-group + table.hotkeys-table + tr + th Name + th ID + th Hotkey + ng-container(*ngFor='let hotkey of hotkeyDescriptions') + tr(*ngIf='!hotkeyFilter || hotkeyFilterFn(hotkey, hotkeyFilter)') + td {{hotkey.name}} + td {{hotkey.id}} + td.pr-5 + multi-hotkey-input( + [model]='getHotkey(hotkey.id) || []', + (modelChange)='setHotkey(hotkey.id, $event)' + ) diff --git a/terminus-settings/src/components/hotkeySettingsTab.component.scss b/terminus-settings/src/components/hotkeySettingsTab.component.scss new file mode 100644 index 00000000..6205aa1b --- /dev/null +++ b/terminus-settings/src/components/hotkeySettingsTab.component.scss @@ -0,0 +1,7 @@ +.hotkeys-table { + margin-top: 30px; + + td, th { + padding: 5px 10px; + } +} diff --git a/terminus-settings/src/components/hotkeySettingsTab.component.ts b/terminus-settings/src/components/hotkeySettingsTab.component.ts new file mode 100644 index 00000000..f2b9e2c8 --- /dev/null +++ b/terminus-settings/src/components/hotkeySettingsTab.component.ts @@ -0,0 +1,57 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { Component, NgZone } from '@angular/core' +import { + ConfigService, + HotkeyDescription, + HotkeysService, + HostAppService, +} from 'terminus-core' + +/** @hidden */ +@Component({ + selector: 'hotkey-settings-tab', + template: require('./hotkeySettingsTab.component.pug'), + styles: [ + require('./hotkeySettingsTab.component.scss'), + ], +}) +export class HotkeySettingsTabComponent { + hotkeyFilter = '' + hotkeyDescriptions: HotkeyDescription[] + + constructor ( + public config: ConfigService, + public hostApp: HostAppService, + public zone: NgZone, + hotkeys: HotkeysService, + ) { + hotkeys.getHotkeyDescriptions().then(descriptions => { + this.hotkeyDescriptions = descriptions + }) + } + + getHotkey (id: string) { + let ptr = this.config.store.hotkeys + for (const token of id.split(/\./g)) { + ptr = ptr[token] + } + return ptr + } + + setHotkey (id: string, value) { + let ptr = this.config.store + let prop = 'hotkeys' + for (const token of id.split(/\./g)) { + ptr = ptr[prop] + prop = token + } + ptr[prop] = value + this.config.save() + } + + hotkeyFilterFn (hotkey: HotkeyDescription, query: string): boolean { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + const s = hotkey.name + (this.getHotkey(hotkey.id) || []).toString() + return s.toLowerCase().includes(query.toLowerCase()) + } +} diff --git a/terminus-settings/src/components/settingsTab.component.pug b/terminus-settings/src/components/settingsTab.component.pug index 6d1e2611..f9581095 100644 --- a/terminus-settings/src/components/settingsTab.component.pug +++ b/terminus-settings/src/components/settingsTab.component.pug @@ -7,135 +7,38 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic i.fas.fa-fw.fa-window-maximize.mr-2 | Application ng-template(ngbNavContent) - .d-flex.align-items-center.flex-wrap.mb-4 - h1.terminus-title.mb-2.mr-2 Terminus - sup α + .terminus-logo.mt-3 + h1.terminus-title Terminus + sup α - .text-muted.mr-auto {{homeBase.appVersion}} + .text-center + .text-muted {{homeBase.appVersion}} - div + .mb-5.mt-3 button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()') i.fab.fa-github span GitHub - button.btn.btn-secondary((click)='homeBase.reportBug()') + button.btn.btn-secondary.mr-3((click)='homeBase.reportBug()') i.fas.fa-bug span Report a problem - .form-line - .header - .title Theme - select.form-control( - [(ngModel)]='config.store.appearance.theme', - (ngModelChange)='saveConfiguration()', - ) - option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}} + button.btn.btn-secondary( + *ngIf='!updateAvailable', + (click)='checkForUpdates()', + [disabled]='checkingForUpdate' + ) + i.fas.fa-sync( + [class.fa-spin]='checkingForUpdate' + ) + span Check for updates - .form-line - .header - .title Tabs location - .btn-group( - [(ngModel)]='config.store.appearance.tabsLocation', - (ngModelChange)='saveConfiguration()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"top"' - ) - | Top - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"bottom"' - ) - | Bottom - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"left"' - ) - | Left - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"right"' - ) - | Right - - .form-line - .header - .title Tabs width - .btn-group( - [(ngModel)]='config.store.appearance.flexTabs', - (ngModelChange)='saveConfiguration()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='true' - ) - | Dynamic - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='false' - ) - | Fixed - - .form-line - .header - .title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background - .title(*ngIf='hostApp.platform === Platform.macOS') Vibrancy - .description Gives the window a blurred transparent background - - toggle( - [(ngModel)]='config.store.appearance.vibrancy', - (ngModelChange)='saveConfiguration()' - ) - - .form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported') - .header - .title Background type - .btn-group( - [(ngModel)]='config.store.appearance.vibrancyType', - (ngModelChange)='saveConfiguration()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"blur"' - ) - | Blur - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"fluent"' - ) - | Fluent - - .form-line - .header - .title Opacity - input( - type='range', - [(ngModel)]='config.store.appearance.opacity', - (ngModelChange)='saveConfiguration(); (hostApp.platform === Platform.Linux && config.requestRestart())', - min='0.4', - max='1', - step='0.01' - ) + button.btn.btn-info( + *ngIf='updateAvailable', + (click)='updater.update()', + ) + i.fas.fa-sync + span Update .form-line(*ngIf='hostApp.platform !== Platform.Linux') .header @@ -143,151 +46,6 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic .description Allows quickly opening a terminal in the selected folder toggle([ngModel]='isShellIntegrationInstalled', (ngModelChange)='toggleShellIntegration()') - .form-line - .header - .title Window frame - .description Whether a custom window or an OS native window should be used - - .btn-group( - [(ngModel)]='config.store.appearance.frame', - (ngModelChange)='saveConfiguration(true)', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"native"' - ) - | Native - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"thin"' - ) - | Thin - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"full"' - ) - | Full - - .form-line - .header - .title Dock the terminal - .description Snaps the window to a side of the screen - - .btn-group( - [(ngModel)]='config.store.appearance.dock', - (ngModelChange)='saveConfiguration(); docking.dock()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"off"' - ) - | Off - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"top"' - ) - | Top - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"left"' - ) - | Left - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"right"' - ) - | Right - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"bottom"' - ) - | Bottom - - .ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') - .header - .title Display on - .description Snaps the window to a side of the screen - - div( - [(ngModel)]='config.store.appearance.dockScreen', - (ngModelChange)='saveConfiguration(); docking.dock()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - value='current' - ) - | Current - label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='screen.id' - ) - | {{screen.name}} - - .ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') - .header - .title Dock always on top - .description Keep docked terminal always on top - toggle( - [(ngModel)]='config.store.appearance.dockAlwaysOnTop', - (ngModelChange)='saveConfiguration(); docking.dock()', - ) - - .ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') - .header - .title Docked terminal size - input( - type='range', - [(ngModel)]='config.store.appearance.dockFill', - (mouseup)='saveConfiguration(); docking.dock()', - min='0.05', - max='1', - step='0.01' - ) - - .ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') - .header - .title Docked terminal space - input( - type='range', - [(ngModel)]='config.store.appearance.dockSpace', - (mouseup)='saveConfiguration(); docking.dock()', - min='0.2', - max='1', - step='0.01' - ) - - .ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') - .header - .title Hide dock on blur - .description Hides the docked terminal when you click away. - toggle( - [(ngModel)]='config.store.appearance.dockHideOnBlur', - (ngModelChange)='saveConfiguration(); ', - ) - .form-line .header .title Debugging @@ -311,43 +69,6 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic .description Enable automatic installation of updates when they become available. toggle([(ngModel)]='config.store.enableAutomaticUpdates', (ngModelChange)='saveConfiguration()') - .form-line - .header - .title Custom CSS - textarea.form-control( - [(ngModel)]='config.store.appearance.css', - (ngModelChange)='saveConfiguration()', - ) - - li(ngbNavItem='hotkeys') - a(ngbNavLink) - i.fas.fa-fw.fa-keyboard.mr-2 - | Hotkeys - ng-template(ngbNavContent) - h3.mb-3 Hotkeys - - .input-group.mb-4 - .input-group-prepend - .input-group-text - i.fas.fa-fw.fa-search - input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter') - - .form-group - table.hotkeys-table - tr - th Name - th ID - th Hotkey - ng-container(*ngFor='let hotkey of hotkeyDescriptions') - tr(*ngIf='!hotkeyFilter || hotkeyFilterFn(hotkey, hotkeyFilter)') - td {{hotkey.name}} - td {{hotkey.id}} - td.pr-5 - multi-hotkey-input( - [model]='getHotkey(hotkey.id) || []', - (modelChange)='setHotkey(hotkey.id, $event); saveConfiguration(); docking.dock()' - ) - li(*ngFor='let provider of settingsProviders', [ngbNavItem]='provider.id') a(ngbNavLink) i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}') diff --git a/terminus-settings/src/components/settingsTab.component.scss b/terminus-settings/src/components/settingsTab.component.scss index 7d255921..7f6385e5 100644 --- a/terminus-settings/src/components/settingsTab.component.scss +++ b/terminus-settings/src/components/settingsTab.component.scss @@ -24,7 +24,7 @@ padding: 20px 30px; overflow-y: auto; - > .tab-pane { + > ::ng-deep .tab-pane { height: 100%; } } @@ -35,15 +35,6 @@ } } - -.hotkeys-table { - margin-top: 30px; - - td, th { - padding: 5px 10px; - } -} - textarea { font-family: 'Source Code Pro', monospace; min-height: 120px; diff --git a/terminus-settings/src/components/settingsTab.component.ts b/terminus-settings/src/components/settingsTab.component.ts index 379c2121..52de1de3 100644 --- a/terminus-settings/src/components/settingsTab.component.ts +++ b/terminus-settings/src/components/settingsTab.component.ts @@ -5,18 +5,13 @@ import { Subscription } from 'rxjs' import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core' import { ElectronService, - DockingService, ConfigService, - HotkeyDescription, - HotkeysService, BaseTabComponent, - Theme, HostAppService, Platform, HomeBaseService, ShellIntegrationService, - isWindowsBuild, - WIN_BUILD_FLUENT_BG_SUPPORTED, + UpdaterService, } from 'terminus-core' import { SettingsTabProvider } from '../api' @@ -31,34 +26,29 @@ import { SettingsTabProvider } from '../api' }) export class SettingsTabComponent extends BaseTabComponent { @Input() activeTab: string - hotkeyFilter = '' - hotkeyDescriptions: HotkeyDescription[] - screens: any[] Platform = Platform configDefaults: any configFile: string isShellIntegrationInstalled = false - isFluentVibrancySupported = false + checkingForUpdate = false + updateAvailable = false @HostBinding('class.pad-window-controls') padWindowControls = false private configSubscription: Subscription constructor ( public config: ConfigService, private electron: ElectronService, - public docking: DockingService, public hostApp: HostAppService, public homeBase: HomeBaseService, public shellIntegration: ShellIntegrationService, public zone: NgZone, - hotkeys: HotkeysService, + private updater: UpdaterService, @Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[], - @Inject(Theme) public themes: Theme[], ) { super() this.setTitle('Settings') - this.screens = this.docking.getScreens() this.settingsProviders = config.enabledServices(this.settingsProviders) - this.themes = config.enabledServices(this.themes) + this.settingsProviders.sort((a, b) => a.title.localeCompare(b.title)) this.configDefaults = yaml.dump(config.getDefaults()) @@ -70,16 +60,6 @@ export class SettingsTabComponent extends BaseTabComponent { this.configSubscription = config.changed$.subscribe(onConfigChange) onConfigChange() - - hostApp.displaysChanged$.subscribe(() => { - this.zone.run(() => this.screens = this.docking.getScreens()) - }) - - hotkeys.getHotkeyDescriptions().then(descriptions => { - this.hotkeyDescriptions = descriptions - }) - - this.isFluentVibrancySupported = isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED) } async ngOnInit () { @@ -131,27 +111,9 @@ export class SettingsTabComponent extends BaseTabComponent { } } - getHotkey (id: string) { - let ptr = this.config.store.hotkeys - for (const token of id.split(/\./g)) { - ptr = ptr[token] - } - return ptr - } - - setHotkey (id: string, value) { - let ptr = this.config.store - let prop = 'hotkeys' - for (const token of id.split(/\./g)) { - ptr = ptr[prop] - prop = token - } - ptr[prop] = value - } - - hotkeyFilterFn (hotkey: HotkeyDescription, query: string): boolean { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - const s = hotkey.name + (this.getHotkey(hotkey.id) || []).toString() - return s.toLowerCase().includes(query.toLowerCase()) + async checkForUpdates () { + this.checkingForUpdate = true + this.updateAvailable = await this.updater.check() + this.checkingForUpdate = false } } diff --git a/terminus-settings/src/components/windowSettingsTab.component.pug b/terminus-settings/src/components/windowSettingsTab.component.pug new file mode 100644 index 00000000..d23455fe --- /dev/null +++ b/terminus-settings/src/components/windowSettingsTab.component.pug @@ -0,0 +1,279 @@ +h3.mb-3 Window + +.form-line + .header + .title Theme + select.form-control( + [(ngModel)]='config.store.appearance.theme', + (ngModelChange)='saveConfiguration()', + ) + option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}} + +.form-line + .header + .title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background + .title(*ngIf='hostApp.platform === Platform.macOS') Vibrancy + .description Gives the window a blurred transparent background + + toggle( + [(ngModel)]='config.store.appearance.vibrancy', + (ngModelChange)='saveConfiguration()' + ) + +.form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported') + .header + .title Background type + .btn-group( + [(ngModel)]='config.store.appearance.vibrancyType', + (ngModelChange)='saveConfiguration()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"blur"' + ) + | Blur + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"fluent"' + ) + | Fluent + +.form-line + .header + .title Opacity + input( + type='range', + [(ngModel)]='config.store.appearance.opacity', + (ngModelChange)='saveConfiguration(); (hostApp.platform === Platform.Linux && config.requestRestart())', + min='0.4', + max='1', + step='0.01' + ) + +.form-line + .header + .title Window frame + .description Whether a custom window or an OS native window should be used + + .btn-group( + [(ngModel)]='config.store.appearance.frame', + (ngModelChange)='saveConfiguration(true)', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"native"' + ) + | Native + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"thin"' + ) + | Thin + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"full"' + ) + | Full + +.form-line + .header + .title Dock the terminal + .description Snaps the window to a side of the screen + + .btn-group( + [(ngModel)]='config.store.appearance.dock', + (ngModelChange)='saveConfiguration(); docking.dock()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"off"' + ) + | Off + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"top"' + ) + | Top + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"left"' + ) + | Left + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"right"' + ) + | Right + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"bottom"' + ) + | Bottom + +.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') + .header + .title Display on + .description Snaps the window to a side of the screen + + div( + [(ngModel)]='config.store.appearance.dockScreen', + (ngModelChange)='saveConfiguration(); docking.dock()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + value='current' + ) + | Current + label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='screen.id' + ) + | {{screen.name}} + +.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') + .header + .title Dock always on top + .description Keep docked terminal always on top + toggle( + [(ngModel)]='config.store.appearance.dockAlwaysOnTop', + (ngModelChange)='saveConfiguration(); docking.dock()', + ) + +.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') + .header + .title Docked terminal size + input( + type='range', + [(ngModel)]='config.store.appearance.dockFill', + (mouseup)='saveConfiguration(); docking.dock()', + min='0.05', + max='1', + step='0.01' + ) + +.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') + .header + .title Docked terminal space + input( + type='range', + [(ngModel)]='config.store.appearance.dockSpace', + (mouseup)='saveConfiguration(); docking.dock()', + min='0.2', + max='1', + step='0.01' + ) + +.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') + .header + .title Hide dock on blur + .description Hides the docked terminal when you click away. + toggle( + [(ngModel)]='config.store.appearance.dockHideOnBlur', + (ngModelChange)='saveConfiguration(); ', + ) + +.form-line + .header + .title Tabs location + .btn-group( + [(ngModel)]='config.store.appearance.tabsLocation', + (ngModelChange)='saveConfiguration()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"top"' + ) + | Top + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"bottom"' + ) + | Bottom + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"left"' + ) + | Left + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"right"' + ) + | Right + +.form-line + .header + .title Tabs width + .btn-group( + [(ngModel)]='config.store.appearance.flexTabs', + (ngModelChange)='saveConfiguration()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='true' + ) + | Dynamic + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='false' + ) + | Fixed + +.form-line + .header + .title Hide tab index + + toggle( + [(ngModel)]='config.store.terminal.hideTabIndex', + (ngModelChange)='config.save();', + ) + +.form-line + .header + .title Hide tab close button + + toggle( + [(ngModel)]='config.store.terminal.hideCloseButton', + (ngModelChange)='config.save();', + ) diff --git a/terminus-settings/src/components/windowSettingsTab.component.ts b/terminus-settings/src/components/windowSettingsTab.component.ts new file mode 100644 index 00000000..46e6cc41 --- /dev/null +++ b/terminus-settings/src/components/windowSettingsTab.component.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { debounce } from 'utils-decorators/dist/cjs' +import { Component, Inject, NgZone } from '@angular/core' +import { + DockingService, + ConfigService, + Theme, + HostAppService, + Platform, + isWindowsBuild, + WIN_BUILD_FLUENT_BG_SUPPORTED, +} from 'terminus-core' + + +/** @hidden */ +@Component({ + selector: 'window-settings-tab', + template: require('./windowSettingsTab.component.pug'), +}) +export class WindowSettingsTabComponent { + screens: any[] + Platform = Platform + isFluentVibrancySupported = false + + constructor ( + public config: ConfigService, + public docking: DockingService, + public hostApp: HostAppService, + public zone: NgZone, + @Inject(Theme) public themes: Theme[], + ) { + this.screens = this.docking.getScreens() + this.themes = config.enabledServices(this.themes) + + hostApp.displaysChanged$.subscribe(() => { + this.zone.run(() => this.screens = this.docking.getScreens()) + }) + + this.isFluentVibrancySupported = isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED) + } + + @debounce(500) + saveConfiguration (requireRestart?: boolean) { + this.config.save() + if (requireRestart) { + this.config.requestRestart() + } + } +} diff --git a/terminus-settings/src/index.ts b/terminus-settings/src/index.ts index 5ffb63fa..0ef49ee1 100644 --- a/terminus-settings/src/index.ts +++ b/terminus-settings/src/index.ts @@ -6,13 +6,17 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import TerminusCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider } from 'terminus-core' import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component' +import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component' import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component' import { SettingsTabComponent } from './components/settingsTab.component' import { SettingsTabBodyComponent } from './components/settingsTabBody.component' +import { WindowSettingsTabComponent } from './components/windowSettingsTab.component' +import { SettingsTabProvider } from './api' import { ButtonProvider } from './buttonProvider' import { SettingsHotkeyProvider } from './hotkeys' import { SettingsConfigProvider } from './config' +import { HotkeySettingsTabProvider, WindowSettingsTabProvider } from './settings' /** @hidden */ @NgModule({ @@ -26,16 +30,22 @@ import { SettingsConfigProvider } from './config' { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ConfigProvider, useClass: SettingsConfigProvider, multi: true }, { provide: HotkeyProvider, useClass: SettingsHotkeyProvider, multi: true }, + { provide: SettingsTabProvider, useClass: HotkeySettingsTabProvider, multi: true }, + { provide: SettingsTabProvider, useClass: WindowSettingsTabProvider, multi: true }, ], entryComponents: [ HotkeyInputModalComponent, + HotkeySettingsTabComponent, SettingsTabComponent, + WindowSettingsTabComponent, ], declarations: [ HotkeyInputModalComponent, + HotkeySettingsTabComponent, MultiHotkeyInputComponent, SettingsTabComponent, SettingsTabBodyComponent, + WindowSettingsTabComponent, ], }) export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class diff --git a/terminus-settings/src/settings.ts b/terminus-settings/src/settings.ts new file mode 100644 index 00000000..6e618f83 --- /dev/null +++ b/terminus-settings/src/settings.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core' +import { SettingsTabProvider } from './api' +import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component' +import { WindowSettingsTabComponent } from './components/windowSettingsTab.component' + +/** @hidden */ +@Injectable() +export class HotkeySettingsTabProvider extends SettingsTabProvider { + id = 'hotkeys' + icon = 'keyboard' + title = 'Hotkeys' + + getComponentType (): any { + return HotkeySettingsTabComponent + } +} + + +/** @hidden */ +@Injectable() +export class WindowSettingsTabProvider extends SettingsTabProvider { + id = 'window' + icon = 'window-maximize' + title = 'Window' + + getComponentType (): any { + return WindowSettingsTabComponent + } +} diff --git a/terminus-terminal/src/components/appearanceSettingsTab.component.pug b/terminus-terminal/src/components/appearanceSettingsTab.component.pug index 04dba241..f1ed1f71 100644 --- a/terminus-terminal/src/components/appearanceSettingsTab.component.pug +++ b/terminus-terminal/src/components/appearanceSettingsTab.component.pug @@ -33,19 +33,6 @@ h3.mb-3 Appearance .col-12.col-md-6 color-scheme-preview([scheme]='config.store.terminal.colorScheme', [fontPreview]='true') -.form-line - .header - .title Frontend - .description Switches terminal frontend implementation (experimental) - - select.form-control( - [(ngModel)]='config.store.terminal.frontend', - (ngModelChange)='config.save()', - ) - option(value='hterm') hterm - option(value='xterm') xterm - option(value='xterm-webgl') xterm (WebGL) - .form-line .header .title Terminal background @@ -110,24 +97,6 @@ h3.mb-3 Appearance (ngModelChange)='config.save()', ) -.form-line - .header - .title Hide tab index - - toggle( - [(ngModel)]='config.store.terminal.hideTabIndex', - (ngModelChange)='config.save();', - ) - -.form-line - .header - .title Hide tab close button - - toggle( - [(ngModel)]='config.store.terminal.hideCloseButton', - (ngModelChange)='config.save();', - ) - .form-line .header .title Fallback font @@ -139,3 +108,13 @@ h3.mb-3 Appearance [(ngModel)]='config.store.terminal.fallbackFont', (ngModelChange)='config.save()' ) + + +.form-line + .header + .title Custom CSS + +textarea.form-control( + [(ngModel)]='config.store.appearance.css', + (ngModelChange)='saveConfiguration()', +) diff --git a/terminus-terminal/src/components/appearanceSettingsTab.component.scss b/terminus-terminal/src/components/appearanceSettingsTab.component.scss index 632bbe92..f9c7d0ac 100644 --- a/terminus-terminal/src/components/appearanceSettingsTab.component.scss +++ b/terminus-terminal/src/components/appearanceSettingsTab.component.scss @@ -2,3 +2,8 @@ color-scheme-preview { flex-shrink: 0; margin-bottom: 20px; } + +textarea { + font-family: 'Source Code Pro', monospace; + min-height: 120px; +} diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.pug b/terminus-terminal/src/components/terminalSettingsTab.component.pug index 3314af10..8d193f93 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.pug +++ b/terminus-terminal/src/components/terminalSettingsTab.component.pug @@ -1,5 +1,18 @@ h3.mb-3 Terminal +.form-line + .header + .title Frontend + .description Switches terminal frontend implementation (experimental) + + select.form-control( + [(ngModel)]='config.store.terminal.frontend', + (ngModelChange)='config.save()', + ) + option(value='hterm') hterm + option(value='xterm') xterm + option(value='xterm-webgl') xterm (WebGL) + .form-line .header .title Terminal bell @@ -116,7 +129,7 @@ h3.mb-3 Terminal [(ngModel)]='config.store.terminal.scrollOnInput', (ngModelChange)='config.save()', ) - + .form-line .header .title Use Alt key as the Meta key