diff --git a/terminus-core/src/components/checkbox.component.pug b/terminus-core/src/components/checkbox.component.pug new file mode 100644 index 00000000..cb6b09fc --- /dev/null +++ b/terminus-core/src/components/checkbox.component.pug @@ -0,0 +1,4 @@ +.icon((click)='click()', tabindex='0', [class.active]='model', (keyup.space)='click()') + i.fa.fa-square-o.off + i.fa.fa-check-square.on +.text((click)='click()') {{text}} diff --git a/terminus-core/src/components/checkbox.component.scss b/terminus-core/src/components/checkbox.component.scss new file mode 100644 index 00000000..a1a10352 --- /dev/null +++ b/terminus-core/src/components/checkbox.component.scss @@ -0,0 +1,51 @@ +:host { + cursor: pointer; + margin: 5px 0; + + &:focus { + background: rgba(255,255,255,.05); + border-radius: 5px; + } + + &:active { + background: rgba(255,255,255,.1); + border-radius: 3px; + } + + &[disabled] { + opacity: 0.5; + } + + display: flex; + flex-direction: row; + align-items: center; + + .icon { + position: relative; + flex: none; + width: 14px; + height: 14px; + + i { + position: absolute; + left: 0; + top: -2px; + transition: 0.25s opacity; + display: block; + font-size: 18px; + } + + i.on, &.active i.off { + opacity: 0; + } + + i.off, &.active i.on { + opacity: 1; + } + } + + .text { + flex: auto; + margin-left: 8px; + } +} diff --git a/terminus-core/src/components/checkbox.component.ts b/terminus-core/src/components/checkbox.component.ts new file mode 100644 index 00000000..b6df6b55 --- /dev/null +++ b/terminus-core/src/components/checkbox.component.ts @@ -0,0 +1,45 @@ +import { NgZone, Component, Input } from '@angular/core' +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' + +@Component({ + selector: 'checkbox', + template: require('./checkbox.component.pug'), + styles: [require('./checkbox.component.scss')], + providers: [ + { provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true }, + ] +}) +export class CheckboxComponent implements ControlValueAccessor { + @Input() model: boolean + @Input() disabled: boolean + @Input() text: string + private changed = new Array<(val: boolean) => void>() + + click () { + NgZone.assertInAngularZone() + if (this.disabled) { + return + } + + this.model = !this.model + for (let fx of this.changed) { + fx(this.model) + } + } + + writeValue (obj: any) { + this.model = obj + } + + registerOnChange (fn: any): void { + this.changed.push(fn) + } + + registerOnTouched (fn: any): void { + this.changed.push(fn) + } + + setDisabledState (isDisabled: boolean) { + this.disabled = isDisabled + } +} diff --git a/terminus-core/src/index.ts b/terminus-core/src/index.ts index 0c6e7234..7a9898bd 100644 --- a/terminus-core/src/index.ts +++ b/terminus-core/src/index.ts @@ -18,6 +18,7 @@ import { TouchbarService } from './services/touchbar.service' import { UpdaterService } from './services/updater.service' import { AppRootComponent } from './components/appRoot.component' +import { CheckboxComponent } from './components/checkbox.component' import { TabBodyComponent } from './components/tabBody.component' import { SafeModeModalComponent } from './components/safeModeModal.component' import { StartPageComponent } from './components/startPage.component' @@ -65,6 +66,7 @@ const PROVIDERS = [ ], declarations: [ AppRootComponent, + CheckboxComponent, StartPageComponent, TabBodyComponent, TabHeaderComponent, @@ -76,6 +78,9 @@ const PROVIDERS = [ entryComponents: [ RenameTabModalComponent, SafeModeModalComponent, + ], + exports: [ + CheckboxComponent ] }) export default class AppModule { diff --git a/terminus-core/src/theme.scss b/terminus-core/src/theme.scss index 9aaa44fa..2fc143f9 100644 --- a/terminus-core/src/theme.scss +++ b/terminus-core/src/theme.scss @@ -47,6 +47,7 @@ $input-color-placeholder: #333; $input-border-color: #344; //$input-box-shadow: inset 0 1px 1px rgba($black,.075); $input-border-radius: 0; +$custom-select-border-radius: 0; $input-bg-focus: $input-bg; //$input-border-focus: lighten($brand-primary, 25%); //$input-box-shadow-focus: $input-box-shadow, rgba($input-border-focus, .6); @@ -83,6 +84,8 @@ $alert-danger-bg: $body-bg2; $alert-danger-text: $red; $alert-danger-border: $red; +$headings-font-weight: lighter; +$headings-color: #eee; @import '~bootstrap/scss/bootstrap.scss'; @@ -147,6 +150,7 @@ app-root { } &.active { + color: white; background: $body-bg; border-left: 1px solid $border-color; border-right: 1px solid $border-color; @@ -328,3 +332,15 @@ ngb-tabset .tab-content { margin-left: 10px; } } + +select.form-control { + -webkit-appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-position: 100% 50%; + background-repeat: no-repeat; + padding-right: 30px; +} + +checkbox i.on { + color: $blue; +} diff --git a/terminus-settings/src/components/multiHotkeyInput.component.scss b/terminus-settings/src/components/multiHotkeyInput.component.scss index 8923c351..642987cb 100644 --- a/terminus-settings/src/components/multiHotkeyInput.component.scss +++ b/terminus-settings/src/components/multiHotkeyInput.component.scss @@ -1,5 +1,10 @@ :host { display: flex; + flex-wrap: nowrap; + + &:hover .add { + display: initial; + } } .item { @@ -22,4 +27,5 @@ .add { flex: auto; + display: none; } diff --git a/terminus-settings/src/components/settingsTab.component.pug b/terminus-settings/src/components/settingsTab.component.pug index df8ed239..1b5e9942 100644 --- a/terminus-settings/src/components/settingsTab.component.pug +++ b/terminus-settings/src/components/settingsTab.component.pug @@ -5,6 +5,7 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab') ng-template(ngbTabTitle) | Application ng-template(ngbTabContent) + h3.mb-3 Application .row .col.col-lg-6 .form-group @@ -168,6 +169,7 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab') ng-template(ngbTabTitle) | Hotkeys ng-template(ngbTabContent) + h3.mb-3 Hotkeys input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter') .form-group table.hotkeys-table diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.pug b/terminus-terminal/src/components/terminalSettingsTab.component.pug index 923ae16a..5e9ce00f 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.pug +++ b/terminus-terminal/src/components/terminalSettingsTab.component.pug @@ -1,5 +1,158 @@ -h3.mb-2 Appearance +h3.mb-3 Appearance .row + .col-md-6 + .form-group + label Font + .row + .col-8 + input.form-control( + type='text', + [ngbTypeahead]='fontAutocomplete', + [(ngModel)]='config.store.terminal.font', + (ngModelChange)='config.save()', + ) + .col-4 + input.form-control( + type='number', + [(ngModel)]='config.store.terminal.fontSize', + (ngModelChange)='config.save()', + ) + + div + checkbox( + text='Enable font ligatures', + [(ngModel)]='config.store.terminal.ligatures', + (ngModelChange)='config.save()', + ) + + .form-group(*ngIf='!editingColorScheme') + label Color scheme + .input-group + select.form-control( + [compareWith]='equalComparator', + [(ngModel)]='config.store.terminal.colorScheme', + (ngModelChange)='config.save()', + ) + option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}} + option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}} + .input-group-btn + button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)') Edit + .input-group-btn + button.btn.btn-outline-danger( + (click)='deleteScheme(config.store.terminal.colorScheme)', + *ngIf='isCustomScheme(config.store.terminal.colorScheme)' + ) + i.fa.fa-trash-o + + .form-group(*ngIf='editingColorScheme') + label Editing + .input-group + input.form-control(type='text', [(ngModel)]='editingColorScheme.name') + .input-group-btn + button.btn.btn-secondary((click)='saveScheme()') Save + .input-group-btn + button.btn.btn-secondary((click)='cancelEditing()') Cancel + + + .form-group(*ngIf='editingColorScheme') + color-picker( + '[(model)]'='editingColorScheme.foreground', + (modelChange)='config.save(); schemeChanged = true', + title='FG', + ) + color-picker( + '[(model)]'='editingColorScheme.background', + (modelChange)='config.save(); schemeChanged = true', + title='BG', + ) + color-picker( + '[(model)]'='editingColorScheme.cursor', + (modelChange)='config.save(); schemeChanged = true', + title='CU', + ) + color-picker( + *ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy', + '[(model)]'='editingColorScheme.colors[idx]', + (modelChange)='config.save(); schemeChanged = true', + [title]='idx', + ) + + .form-group + label Terminal background + br + .btn-group( + [(ngModel)]='config.store.terminal.background', + (ngModelChange)='config.save()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"theme"' + ) + | From theme + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"colorScheme"' + ) + | From colors + + .d-flex + .form-group.mr-3 + label Cursor shape + br + .btn-group( + [(ngModel)]='config.store.terminal.cursor', + (ngModelChange)='config.save()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"block"' + ) + | █ + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"beam"' + ) + | | + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"underline"' + ) + | ▁ + + .form-group + label Blink cursor + br + .btn-group( + [(ngModel)]='config.store.terminal.cursorBlink', + (ngModelChange)='config.save()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='false' + ) + | Off + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='true' + ) + | On .col-md-6 .form-group .appearance-preview( @@ -7,6 +160,8 @@ h3.mb-2 Appearance [style.font-size]='config.store.terminal.fontSize + "px"', [style.background-color]='(config.store.terminal.background == "theme") ? null : config.store.terminal.colorScheme.background', [style.color]='config.store.terminal.colorScheme.foreground', + [style.font-feature-settings]='\'"liga" \' + config.store.terminal.ligatures ? 1 : 0', + [style.font-variant-ligatures]='config.store.terminal.ligatures ? "initial" : "none"', ) div span([style.background-color]='config.store.terminal.colorScheme.colors[0]')   @@ -85,133 +240,7 @@ h3.mb-2 Appearance span rm -rf / span([style.background-color]='config.store.terminal.colorScheme.cursor')   - - .col-md-6 - .form-group - label Font - .row - .col-8 - input.form-control( - type='text', - [ngbTypeahead]='fontAutocomplete', - '[(ngModel)]'='config.store.terminal.font', - (ngModelChange)='config.save()', - ) - .col-4 - input.form-control( - type='number', - '[(ngModel)]'='config.store.terminal.fontSize', - (ngModelChange)='config.save()', - ) - small.form-text.text-muted Font to be used in the terminal - - .form-group(*ngIf='!editingColorScheme') - label Color scheme - .input-group - select.form-control( - [compareWith]='equalComparator', - '[(ngModel)]'='config.store.terminal.colorScheme', - (ngModelChange)='config.save()', - ) - option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}} - option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}} - .input-group-btn - button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)') Edit - .input-group-btn - button.btn.btn-outline-danger( - (click)='deleteScheme(config.store.terminal.colorScheme)', - *ngIf='isCustomScheme(config.store.terminal.colorScheme)' - ) - i.fa.fa-trash-o - - .form-group(*ngIf='editingColorScheme') - label Editing - .input-group - input.form-control(type='text', '[(ngModel)]'='editingColorScheme.name') - .input-group-btn - button.btn.btn-secondary((click)='saveScheme()') Save - .input-group-btn - button.btn.btn-secondary((click)='cancelEditing()') Cancel - - - .form-group(*ngIf='editingColorScheme') - color-picker( - '[(model)]'='editingColorScheme.foreground', - (modelChange)='config.save(); schemeChanged = true', - title='FG', - ) - color-picker( - '[(model)]'='editingColorScheme.background', - (modelChange)='config.save(); schemeChanged = true', - title='BG', - ) - color-picker( - '[(model)]'='editingColorScheme.cursor', - (modelChange)='config.save(); schemeChanged = true', - title='CU', - ) - color-picker( - *ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy', - '[(model)]'='editingColorScheme.colors[idx]', - (modelChange)='config.save(); schemeChanged = true', - [title]='idx', - ) - - .d-flex - .form-group.mr-3 - label Terminal background - br - .btn-group( - '[(ngModel)]'='config.store.terminal.background', - (ngModelChange)='config.save()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"theme"' - ) - | From theme - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"colorScheme"' - ) - | From colors - - .form-group - label Cursor shape - br - .btn-group( - [(ngModel)]='config.store.terminal.cursor', - (ngModelChange)='config.save()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"block"' - ) - | █ - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"beam"' - ) - | | - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"underline"' - ) - | ▁ - -h3.mt-2.mb-2 Behaviour +h3.mt-3.mb-3 Shell .row .col-md-6 @@ -219,7 +248,7 @@ h3.mt-2.mb-2 Behaviour .form-group.mr-3 label Shell select.form-control( - '[(ngModel)]'='config.store.terminal.shell', + [(ngModel)]='config.store.terminal.shell', (ngModelChange)='config.save()', ) option( @@ -227,10 +256,10 @@ h3.mt-2.mb-2 Behaviour [ngValue]='shell.id' ) {{shell.name}} - .form-group + .form-group(*ngIf='persistenceProviders.length > 0') label Session persistence select.form-control( - '[(ngModel)]'='config.store.terminal.persistence', + [(ngModel)]='config.store.terminal.persistence', (ngModelChange)='config.save()', ) option([ngValue]='null') Off @@ -243,140 +272,93 @@ h3.mt-2.mb-2 Behaviour label Custom shell input.form-control( type='text', - '[(ngModel)]'='config.store.terminal.customShell', + [(ngModel)]='config.store.terminal.customShell', (ngModelChange)='config.save()', ) - + + .col-md-6 .form-group label Working directory input.form-control( type='text', placeholder='Home directory', - '[(ngModel)]'='config.store.terminal.workingDirectory', + [(ngModel)]='config.store.terminal.workingDirectory', (ngModelChange)='config.save()', ) + + - .form-group - label Auto-open a terminal on app start - br - .btn-group( - '[(ngModel)]'='config.store.terminal.autoOpen', - (ngModelChange)='config.save()', - ngbRadioGroup +h3.mt-3.mb-3 Behaviour + +.form-group + label Terminal bell + br + .btn-group( + [(ngModel)]='config.store.terminal.bell', + (ngModelChange)='config.save()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"off"' ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='false' - ) - | Off - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='true' - ) - | On - - .col-md-6 - .d-flex - .form-group.mr-3 - label Terminal bell - br - .btn-group( - '[(ngModel)]'='config.store.terminal.bell', - (ngModelChange)='config.save()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"off"' - ) - | Off - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"visual"' - ) - | Visual - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='"audible"' - ) - | Audible - - .form-group - label Blink cursor - br - .btn-group( - '[(ngModel)]'='config.store.terminal.cursorBlink', - (ngModelChange)='config.save()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='false' - ) - | Off - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='true' - ) - | On - - .d-flex - .form-group.mr-3 - label Copy on select - br - .btn-group( - '[(ngModel)]'='config.store.terminal.copyOnSelect', - (ngModelChange)='config.save()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='false' - ) - | Off - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - [value]='true' - ) - | On - - .form-group - label Right click behaviour - br - .btn-group( - '[(ngModel)]'='config.store.terminal.rightClick', - (ngModelChange)='config.save()', - ngbRadioGroup - ) - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - value='menu' - ) - | Menu - label.btn.btn-secondary(ngbButtonLabel) - input( - type='radio', - ngbButton, - value='paste' - ) - | Paste + | Off + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"visual"' + ) + | Visual + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + [value]='"audible"' + ) + | Audible + +.form-group + label Right click behaviour + br + .btn-group( + [(ngModel)]='config.store.terminal.rightClick', + (ngModelChange)='config.save()', + ngbRadioGroup + ) + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + value='menu' + ) + | Context menu + label.btn.btn-secondary(ngbButtonLabel) + input( + type='radio', + ngbButton, + value='paste' + ) + | Paste + + +.form-group + checkbox( + [(ngModel)]='config.store.terminal.autoOpen', + (ngModelChange)='config.save()', + text='Auto-open a terminal on app start', + ) + + checkbox( + [(ngModel)]='config.store.terminal.bracketedPaste', + (ngModelChange)='config.save()', + text='Bracketed paste (requires shell support)', + ) + + checkbox( + [(ngModel)]='config.store.terminal.copyOnSelect', + (ngModelChange)='config.save()', + text='Copy on select', + ) diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.scss b/terminus-terminal/src/components/terminalSettingsTab.component.scss index ef003293..5eb28100 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.scss +++ b/terminus-terminal/src/components/terminalSettingsTab.component.scss @@ -1,5 +1,6 @@ .appearance-preview { - padding: 10px 20px; + padding: 10px 0; + margin-left: 30px; margin: 0 0 10px; overflow: hidden; span { diff --git a/terminus-terminal/src/hotkeys.ts b/terminus-terminal/src/hotkeys.ts index 39e5b579..c73dcb38 100644 --- a/terminus-terminal/src/hotkeys.ts +++ b/terminus-terminal/src/hotkeys.ts @@ -56,5 +56,9 @@ export class TerminalHotkeyProvider extends HotkeyProvider { id: 'new-tab', name: 'New tab', }, + { + id: 'ctrl-c', + name: 'Intelligent Ctrl-C (copy/abort)', + }, ] } diff --git a/terminus-terminal/src/index.ts b/terminus-terminal/src/index.ts index f1cfba7d..dbb5cd11 100644 --- a/terminus-terminal/src/index.ts +++ b/terminus-terminal/src/index.ts @@ -3,6 +3,7 @@ import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { ToastrModule } from 'ngx-toastr' +import TerminusCorePlugin from 'terminus-core' import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core' import { SettingsTabProvider } from 'terminus-settings' @@ -43,6 +44,7 @@ import { hterm } from './hterm' FormsModule, NgbModule, ToastrModule, + TerminusCorePlugin, ], providers: [ SessionsService,