mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-20 02:18:01 +00:00
automatic dark/light theme - fixes #3934
This commit is contained in:
@@ -86,14 +86,18 @@ export interface FileUploadOptions {
|
||||
multiple: boolean
|
||||
}
|
||||
|
||||
export type PlatformTheme = 'light'|'dark'
|
||||
|
||||
export abstract class PlatformService {
|
||||
supportsWindowControls = false
|
||||
|
||||
get fileTransferStarted$ (): Observable<FileTransfer> { return this.fileTransferStarted }
|
||||
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
|
||||
get themeChanged$ (): Observable<PlatformTheme> { return this.themeChanged }
|
||||
|
||||
protected fileTransferStarted = new Subject<FileTransfer>()
|
||||
protected displayMetricsChanged = new Subject<void>()
|
||||
protected themeChanged = new Subject<PlatformTheme>()
|
||||
|
||||
abstract readClipboard (): string
|
||||
abstract setClipboard (content: ClipboardContent): void
|
||||
@@ -169,6 +173,10 @@ export abstract class PlatformService {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
getTheme (): PlatformTheme {
|
||||
return 'dark'
|
||||
}
|
||||
|
||||
abstract getOSRelease (): string
|
||||
abstract getAppVersion (): string
|
||||
abstract openExternal (url: string): void
|
||||
|
@@ -11,7 +11,7 @@ appearance:
|
||||
tabsLocation: top
|
||||
tabsInFullscreen: false
|
||||
cycleTabs: true
|
||||
theme: Standard
|
||||
theme: Follow the color scheme
|
||||
frame: thin
|
||||
css: '/* * { color: blue !important; } */'
|
||||
opacity: 1.0
|
||||
|
@@ -3,6 +3,7 @@ import { Subject, Observable } from 'rxjs'
|
||||
import * as Color from 'color'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
import { Theme } from '../api/theme'
|
||||
import { PlatformService } from '../api/platform'
|
||||
import { NewTheme } from '../theme'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -17,6 +18,7 @@ export class ThemesService {
|
||||
private constructor (
|
||||
private config: ConfigService,
|
||||
private standardTheme: NewTheme,
|
||||
private platform: PlatformService,
|
||||
@Inject(Theme) private themes: Theme[],
|
||||
) {
|
||||
this.rootElementStyleBackup = document.documentElement.style.cssText
|
||||
@@ -24,6 +26,10 @@ export class ThemesService {
|
||||
config.ready$.toPromise().then(() => {
|
||||
this.applyCurrentTheme()
|
||||
this.applyThemeVariables()
|
||||
platform.themeChanged$.subscribe(() => {
|
||||
this.applyCurrentTheme()
|
||||
this.applyThemeVariables()
|
||||
})
|
||||
config.changed$.subscribe(() => {
|
||||
this.applyCurrentTheme()
|
||||
this.applyThemeVariables()
|
||||
@@ -36,7 +42,7 @@ export class ThemesService {
|
||||
document.documentElement.style.cssText = this.rootElementStyleBackup
|
||||
}
|
||||
|
||||
const theme = this.config.store.terminal.colorScheme
|
||||
const theme = this._getActiveColorScheme()
|
||||
const isDark = Color(theme.background).luminosity() < Color(theme.foreground).luminosity()
|
||||
|
||||
function more (some, factor) {
|
||||
@@ -106,8 +112,10 @@ export class ThemesService {
|
||||
|
||||
const themeColors = {
|
||||
primary: theme.colors[accentIndex],
|
||||
secondary: less(theme.background, 0.5).string(),
|
||||
tertiary: theme.colors[8],
|
||||
secondary: isDark
|
||||
? less(theme.background, 0.5).string()
|
||||
: less(theme.background, 0.125).string(),
|
||||
tertiary: more(theme.background, 0.75).string(),
|
||||
warning: theme.colors[3],
|
||||
danger: theme.colors[1],
|
||||
success: theme.colors[2],
|
||||
@@ -184,6 +192,15 @@ export class ThemesService {
|
||||
return this.findTheme(this.config.store.appearance.theme) ?? this.standardTheme
|
||||
}
|
||||
|
||||
/// @hidden
|
||||
_getActiveColorScheme (): any {
|
||||
if (this.platform.getTheme() === 'light') {
|
||||
return this.config.store.terminal.lightColorScheme
|
||||
} else {
|
||||
return this.config.store.terminal.colorScheme
|
||||
}
|
||||
}
|
||||
|
||||
applyTheme (theme: Theme): void {
|
||||
if (!this.styleElement) {
|
||||
this.styleElement = document.createElement('style')
|
||||
|
@@ -110,7 +110,7 @@ body {
|
||||
}
|
||||
|
||||
.nav {
|
||||
--bs-nav-link-color: var(--theme-fg);
|
||||
--bs-nav-link-color: var(--theme-fg-more);
|
||||
--bs-nav-link-hover-color: var(--theme-fg-less);
|
||||
--bs-nav-link-disabled-color: var(--bs-gray);
|
||||
}
|
||||
@@ -119,8 +119,8 @@ body {
|
||||
--bs-nav-tabs-border-width: 2px;
|
||||
--bs-nav-tabs-border-radius: 0;
|
||||
--bs-nav-tabs-link-hover-border-color: var(--bs-body-bg);
|
||||
--bs-nav-tabs-border-color: var(--theme-fg-less-2);
|
||||
--bs-nav-tabs-link-active-color: var(--theme-fg-less-2);
|
||||
--bs-nav-tabs-border-color: var(--theme-fg);
|
||||
--bs-nav-tabs-link-active-color: var(--theme-fg);
|
||||
|
||||
--bs-nav-tabs-link-active-bg: transparent;
|
||||
--bs-nav-tabs-link-active-border-color: transparent;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, PowerSaveBlocker } from 'electron'
|
||||
import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, PowerSaveBlocker, NativeTheme } from 'electron'
|
||||
import * as remote from '@electron/remote'
|
||||
|
||||
export interface MessageBoxResponse {
|
||||
@@ -20,6 +20,7 @@ export class ElectronService {
|
||||
process: any
|
||||
autoUpdater: AutoUpdater
|
||||
powerSaveBlocker: PowerSaveBlocker
|
||||
nativeTheme: NativeTheme
|
||||
TouchBar: typeof TouchBar
|
||||
BrowserWindow: typeof BrowserWindow
|
||||
Menu: typeof Menu
|
||||
@@ -43,5 +44,7 @@ export class ElectronService {
|
||||
this.BrowserWindow = remote.BrowserWindow
|
||||
this.Menu = remote.Menu
|
||||
this.MenuItem = remote.MenuItem
|
||||
this.MenuItem = remote.MenuItem
|
||||
this.nativeTheme = remote.nativeTheme
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import { ElectronService } from '../services/electron.service'
|
||||
import { ElectronHostWindow } from './hostWindow.service'
|
||||
import { ShellIntegrationService } from './shellIntegration.service'
|
||||
import { ElectronHostAppService } from './hostApp.service'
|
||||
import { PlatformTheme } from '../../../tabby-core/src/api/platform'
|
||||
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
||||
|
||||
/* eslint-disable block-scoped-var */
|
||||
@@ -40,6 +41,10 @@ export class ElectronPlatformService extends PlatformService {
|
||||
electron.ipcRenderer.on('host:display-metrics-changed', () => {
|
||||
this.zone.run(() => this.displayMetricsChanged.next())
|
||||
})
|
||||
|
||||
electron.nativeTheme.on('updated', () => {
|
||||
this.zone.run(() => this.themeChanged.next(this.getTheme()))
|
||||
})
|
||||
}
|
||||
|
||||
readClipboard (): string {
|
||||
@@ -243,6 +248,14 @@ export class ElectronPlatformService extends PlatformService {
|
||||
},
|
||||
)).filePaths[0]
|
||||
}
|
||||
|
||||
getTheme (): PlatformTheme {
|
||||
if (this.electron.nativeTheme.shouldUseDarkColors) {
|
||||
return 'dark'
|
||||
} else {
|
||||
return 'light'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ElectronFileUpload extends FileUpload {
|
||||
|
@@ -331,6 +331,10 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
|
||||
this.frontend?.focus()
|
||||
})
|
||||
|
||||
this.subscribeUntilDestroyed(this.platform.themeChanged$, () => {
|
||||
this.configure()
|
||||
})
|
||||
|
||||
const cls: new (..._) => Frontend = {
|
||||
xterm: XTermFrontend,
|
||||
'xterm-webgl': XTermWebGLFrontend,
|
||||
|
@@ -27,9 +27,41 @@ export class DefaultColorSchemes extends TerminalColorSchemeProvider {
|
||||
'#b7fff9',
|
||||
'#ffffff',
|
||||
],
|
||||
selection: undefined,
|
||||
cursorAccent: undefined,
|
||||
}
|
||||
|
||||
static defaultLightColorScheme: TerminalColorScheme = {
|
||||
name: 'Tabby Default Light',
|
||||
foreground: '#4d4d4c',
|
||||
background: '#ffffff',
|
||||
cursor: '#4d4d4c',
|
||||
colors: [
|
||||
'#000000',
|
||||
'#c82829',
|
||||
'#718c00',
|
||||
'#eab700',
|
||||
'#4271ae',
|
||||
'#8959a8',
|
||||
'#3e999f',
|
||||
'#ffffff',
|
||||
'#000000',
|
||||
'#c82829',
|
||||
'#718c00',
|
||||
'#eab700',
|
||||
'#4271ae',
|
||||
'#8959a8',
|
||||
'#3e999f',
|
||||
'#ffffff',
|
||||
],
|
||||
selection: undefined,
|
||||
cursorAccent: undefined,
|
||||
}
|
||||
|
||||
async getSchemes (): Promise<TerminalColorScheme[]> {
|
||||
return [DefaultColorSchemes.defaultColorScheme]
|
||||
return [
|
||||
DefaultColorSchemes.defaultColorScheme,
|
||||
DefaultColorSchemes.defaultLightColorScheme,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,116 @@
|
||||
.head
|
||||
.d-flex.align-items-center(*ngIf='!editing')
|
||||
strong.me-2(translate) Current color scheme
|
||||
span {{getCurrentSchemeName()}}
|
||||
.me-auto
|
||||
.btn-toolbar
|
||||
button.btn.btn-secondary((click)='editScheme()')
|
||||
i.fas.fa-pen
|
||||
span(translate) Edit
|
||||
.me-1
|
||||
button.btn.btn-danger(
|
||||
(click)='deleteScheme(config.store.terminal[this.configKey])',
|
||||
*ngIf='currentCustomScheme'
|
||||
)
|
||||
i.fas.fa-trash
|
||||
span(translate) Delete
|
||||
|
||||
div(*ngIf='editing')
|
||||
.mb-3
|
||||
label(translate) Name
|
||||
input.form-control(type='text', [(ngModel)]='config.store.terminal[this.configKey].name')
|
||||
|
||||
.mb-3
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal[this.configKey].foreground',
|
||||
(modelChange)='config.save()',
|
||||
title='FG',
|
||||
hint='Foreground'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal[this.configKey].background',
|
||||
(modelChange)='config.save()',
|
||||
title='BG',
|
||||
hint='Background'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal[this.configKey].cursor',
|
||||
(modelChange)='config.save()',
|
||||
title='CU',
|
||||
hint='Cursor color'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal[this.configKey].cursorAccent',
|
||||
(modelChange)='config.save()',
|
||||
title='CA',
|
||||
hint='Block cursor foreground'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal[this.configKey].selection',
|
||||
(modelChange)='config.save()',
|
||||
title='SB',
|
||||
hint='Selection background'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal[this.configKey].selectionForeground',
|
||||
(modelChange)='config.save()',
|
||||
title='SF',
|
||||
hint='Selection foreground'
|
||||
)
|
||||
color-picker(
|
||||
*ngFor='let _ of config.store.terminal[this.configKey].colors; let idx = index; trackBy: colorsTrackBy',
|
||||
[(model)]='config.store.terminal[this.configKey].colors[idx]',
|
||||
(modelChange)='config.save()',
|
||||
[title]='idx.toString()',
|
||||
hint='ANSI color {{idx}}'
|
||||
)
|
||||
|
||||
color-scheme-preview([scheme]='config.store.terminal[this.configKey]')
|
||||
|
||||
.btn-toolbar.d-flex.mt-2(*ngIf='editing')
|
||||
.me-auto
|
||||
button.btn.btn-primary((click)='saveScheme()')
|
||||
i.fas.fa-check
|
||||
span(translate) Save
|
||||
.me-1
|
||||
button.btn.btn-secondary((click)='cancelEditing()')
|
||||
i.fas.fa-times
|
||||
span(translate) Cancel
|
||||
|
||||
hr.mt-3.mb-4
|
||||
|
||||
.input-group.mb-3
|
||||
.input-group-text
|
||||
i.fas.fa-fw.fa-search
|
||||
input.form-control(type='search', [placeholder]='"Search color schemes"|translate', [(ngModel)]='filter')
|
||||
|
||||
.body
|
||||
.list-group.list-group-light.mb-3
|
||||
ng-container(*ngFor='let scheme of allColorSchemes')
|
||||
.list-group-item.list-group-item-action(
|
||||
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
|
||||
(click)='selectScheme(scheme)',
|
||||
[class.active]='(currentCustomScheme || currentStockScheme) === scheme'
|
||||
)
|
||||
.d-flex.w-100.align-items-center
|
||||
i.fas.fa-fw([class.fa-check]='(currentCustomScheme || currentStockScheme) === scheme')
|
||||
|
||||
.ms-2
|
||||
|
||||
.me-auto
|
||||
span {{scheme.name}}
|
||||
.badge.text-bg-info.ms-2(*ngIf='customColorSchemes.includes(scheme)', translate) Custom
|
||||
|
||||
div
|
||||
.d-flex
|
||||
.swatch(
|
||||
*ngFor='let index of colorIndexes.slice(0, 8)',
|
||||
[style.background-color]='scheme.colors[index]'
|
||||
)
|
||||
.d-flex
|
||||
.swatch(
|
||||
*ngFor='let index of colorIndexes.slice(8, 16)',
|
||||
[style.background-color]='scheme.colors[index]'
|
||||
)
|
||||
|
||||
color-scheme-preview([scheme]='scheme')
|
@@ -0,0 +1,117 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import deepEqual from 'deep-equal'
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
|
||||
|
||||
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef, HostBinding } from '@angular/core'
|
||||
import { ConfigService, PlatformService, TranslateService } from 'tabby-core'
|
||||
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
|
||||
import { TerminalColorScheme } from '../api/interfaces'
|
||||
|
||||
_('Search color schemes')
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'color-scheme-settings-for-mode',
|
||||
templateUrl: './colorSchemeSettingsForMode.component.pug',
|
||||
styleUrls: ['./colorSchemeSettingsForMode.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ColorSchemeSettingsForModeComponent {
|
||||
@Input() configKey: 'colorScheme'|'lightColorScheme'
|
||||
|
||||
@Input() stockColorSchemes: TerminalColorScheme[] = []
|
||||
@Input() customColorSchemes: TerminalColorScheme[] = []
|
||||
@Input() allColorSchemes: TerminalColorScheme[] = []
|
||||
@Input() filter = ''
|
||||
@Input() editing = false
|
||||
colorIndexes = [...new Array(16).keys()]
|
||||
|
||||
currentStockScheme: TerminalColorScheme|null = null
|
||||
currentCustomScheme: TerminalColorScheme|null = null
|
||||
|
||||
@HostBinding('class.content-box') true
|
||||
|
||||
constructor (
|
||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private platform: PlatformService,
|
||||
private translate: TranslateService,
|
||||
public config: ConfigService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
this.stockColorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
|
||||
this.stockColorSchemes.sort((a, b) => a.name.localeCompare(b.name))
|
||||
this.customColorSchemes = this.config.store.terminal.customColorSchemes
|
||||
this.changeDetector.markForCheck()
|
||||
|
||||
this.update()
|
||||
}
|
||||
|
||||
ngOnChanges () {
|
||||
this.update()
|
||||
}
|
||||
|
||||
selectScheme (scheme: TerminalColorScheme) {
|
||||
this.config.store.terminal[this.configKey] = { ...scheme }
|
||||
this.config.save()
|
||||
this.cancelEditing()
|
||||
this.update()
|
||||
}
|
||||
|
||||
update () {
|
||||
this.currentCustomScheme = this.findMatchingScheme(this.config.store.terminal[this.configKey], this.customColorSchemes)
|
||||
this.currentStockScheme = this.findMatchingScheme(this.config.store.terminal[this.configKey], this.stockColorSchemes)
|
||||
this.allColorSchemes = this.customColorSchemes.concat(this.stockColorSchemes)
|
||||
this.changeDetector.markForCheck()
|
||||
}
|
||||
|
||||
editScheme () {
|
||||
this.editing = true
|
||||
}
|
||||
|
||||
saveScheme () {
|
||||
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== this.config.store.terminal[this.configKey].name)
|
||||
this.customColorSchemes.push(this.config.store.terminal[this.configKey])
|
||||
this.config.store.terminal.customColorSchemes = this.customColorSchemes
|
||||
this.config.save()
|
||||
this.cancelEditing()
|
||||
this.update()
|
||||
}
|
||||
|
||||
cancelEditing () {
|
||||
this.editing = false
|
||||
}
|
||||
|
||||
async deleteScheme (scheme: TerminalColorScheme) {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: this.translate.instant('Delete "{name}"?', scheme),
|
||||
buttons: [
|
||||
this.translate.instant('Delete'),
|
||||
this.translate.instant('Keep'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
},
|
||||
)).response === 0) {
|
||||
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== scheme.name)
|
||||
this.config.store.terminal.customColorSchemes = this.customColorSchemes
|
||||
this.config.save()
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentSchemeName () {
|
||||
return (this.currentCustomScheme ?? this.currentStockScheme)?.name ?? 'Custom'
|
||||
}
|
||||
|
||||
findMatchingScheme (scheme: TerminalColorScheme, schemes: TerminalColorScheme[]) {
|
||||
return schemes.find(x => deepEqual(x, scheme)) ?? null
|
||||
}
|
||||
|
||||
colorsTrackBy (index) {
|
||||
return index
|
||||
}
|
||||
}
|
@@ -1,117 +1,14 @@
|
||||
.head
|
||||
h3.mb-3(translate) Current color scheme
|
||||
h3.mb-3(translate) Color schemes
|
||||
|
||||
.d-flex.align-items-center(*ngIf='!editing')
|
||||
span {{getCurrentSchemeName()}}
|
||||
.me-auto
|
||||
.btn-toolbar
|
||||
button.btn.btn-secondary((click)='editScheme()')
|
||||
i.fas.fa-pen
|
||||
span(translate) Edit
|
||||
.me-1
|
||||
button.btn.btn-danger(
|
||||
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
||||
*ngIf='currentCustomScheme'
|
||||
)
|
||||
i.fas.fa-trash
|
||||
span(translate) Delete
|
||||
ul.nav-tabs(ngbNav, #nav='ngbNav', [activeId]='defaultTab')
|
||||
li(ngbNavItem='dark')
|
||||
a(ngbNavLink, translate) Dark mode
|
||||
ng-template(ngbNavContent)
|
||||
color-scheme-settings-for-mode(configKey='colorScheme')
|
||||
|
||||
div(*ngIf='editing')
|
||||
.mb-3
|
||||
label(translate) Name
|
||||
input.form-control(type='text', [(ngModel)]='config.store.terminal.colorScheme.name')
|
||||
li(ngbNavItem='light')
|
||||
a(ngbNavLink, translate) Light mode
|
||||
ng-template(ngbNavContent)
|
||||
color-scheme-settings-for-mode(configKey='lightColorScheme')
|
||||
|
||||
.mb-3
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal.colorScheme.foreground',
|
||||
(modelChange)='config.save()',
|
||||
title='FG',
|
||||
hint='Foreground'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal.colorScheme.background',
|
||||
(modelChange)='config.save()',
|
||||
title='BG',
|
||||
hint='Background'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal.colorScheme.cursor',
|
||||
(modelChange)='config.save()',
|
||||
title='CU',
|
||||
hint='Cursor color'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal.colorScheme.cursorAccent',
|
||||
(modelChange)='config.save()',
|
||||
title='CA',
|
||||
hint='Block cursor foreground'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal.colorScheme.selection',
|
||||
(modelChange)='config.save()',
|
||||
title='SB',
|
||||
hint='Selection background'
|
||||
)
|
||||
color-picker(
|
||||
[(model)]='config.store.terminal.colorScheme.selectionForeground',
|
||||
(modelChange)='config.save()',
|
||||
title='SF',
|
||||
hint='Selection foreground'
|
||||
)
|
||||
color-picker(
|
||||
*ngFor='let _ of config.store.terminal.colorScheme.colors; let idx = index; trackBy: colorsTrackBy',
|
||||
[(model)]='config.store.terminal.colorScheme.colors[idx]',
|
||||
(modelChange)='config.save()',
|
||||
[title]='idx.toString()',
|
||||
hint='ANSI color {{idx}}'
|
||||
)
|
||||
|
||||
color-scheme-preview([scheme]='config.store.terminal.colorScheme')
|
||||
|
||||
.btn-toolbar.d-flex.mt-2(*ngIf='editing')
|
||||
.me-auto
|
||||
button.btn.btn-primary((click)='saveScheme()')
|
||||
i.fas.fa-check
|
||||
span(translate) Save
|
||||
.me-1
|
||||
button.btn.btn-secondary((click)='cancelEditing()')
|
||||
i.fas.fa-times
|
||||
span(translate) Cancel
|
||||
|
||||
hr.mt-3.mb-4
|
||||
|
||||
.input-group.mb-3
|
||||
.input-group-text
|
||||
i.fas.fa-fw.fa-search
|
||||
input.form-control(type='search', [placeholder]='"Search color schemes"|translate', [(ngModel)]='filter')
|
||||
|
||||
.body
|
||||
.list-group.list-group-light.mb-3
|
||||
ng-container(*ngFor='let scheme of allColorSchemes')
|
||||
.list-group-item.list-group-item-action(
|
||||
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
|
||||
(click)='selectScheme(scheme)',
|
||||
[class.active]='(currentCustomScheme || currentStockScheme) === scheme'
|
||||
)
|
||||
.d-flex.w-100.align-items-center
|
||||
i.fas.fa-fw([class.fa-check]='(currentCustomScheme || currentStockScheme) === scheme')
|
||||
|
||||
.ms-2
|
||||
|
||||
.me-auto
|
||||
span {{scheme.name}}
|
||||
.badge.text-bg-info.ms-2(*ngIf='customColorSchemes.includes(scheme)', translate) Custom
|
||||
|
||||
div
|
||||
.d-flex
|
||||
.swatch(
|
||||
*ngFor='let index of colorIndexes.slice(0, 8)',
|
||||
[style.background-color]='scheme.colors[index]'
|
||||
)
|
||||
.d-flex
|
||||
.swatch(
|
||||
*ngFor='let index of colorIndexes.slice(8, 16)',
|
||||
[style.background-color]='scheme.colors[index]'
|
||||
)
|
||||
|
||||
color-scheme-preview([scheme]='scheme')
|
||||
div([ngbNavOutlet]='nav')
|
||||
|
@@ -1,114 +1,16 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import deepEqual from 'deep-equal'
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
|
||||
|
||||
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef, HostBinding } from '@angular/core'
|
||||
import { ConfigService, PlatformService, TranslateService } from 'tabby-core'
|
||||
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
|
||||
import { TerminalColorScheme } from '../api/interfaces'
|
||||
|
||||
_('Search color schemes')
|
||||
import { Component } from '@angular/core'
|
||||
import { PlatformService } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
templateUrl: './colorSchemeSettingsTab.component.pug',
|
||||
styleUrls: ['./colorSchemeSettingsTab.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ColorSchemeSettingsTabComponent {
|
||||
@Input() stockColorSchemes: TerminalColorScheme[] = []
|
||||
@Input() customColorSchemes: TerminalColorScheme[] = []
|
||||
@Input() allColorSchemes: TerminalColorScheme[] = []
|
||||
@Input() filter = ''
|
||||
@Input() editing = false
|
||||
colorIndexes = [...new Array(16).keys()]
|
||||
|
||||
currentStockScheme: TerminalColorScheme|null = null
|
||||
currentCustomScheme: TerminalColorScheme|null = null
|
||||
|
||||
@HostBinding('class.content-box') true
|
||||
defaultTab = 'dark'
|
||||
|
||||
constructor (
|
||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private platform: PlatformService,
|
||||
private translate: TranslateService,
|
||||
public config: ConfigService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
this.stockColorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
|
||||
this.stockColorSchemes.sort((a, b) => a.name.localeCompare(b.name))
|
||||
this.customColorSchemes = this.config.store.terminal.customColorSchemes
|
||||
this.changeDetector.markForCheck()
|
||||
|
||||
this.update()
|
||||
}
|
||||
|
||||
ngOnChanges () {
|
||||
this.update()
|
||||
}
|
||||
|
||||
selectScheme (scheme: TerminalColorScheme) {
|
||||
this.config.store.terminal.colorScheme = { ...scheme }
|
||||
this.config.save()
|
||||
this.cancelEditing()
|
||||
this.update()
|
||||
}
|
||||
|
||||
update () {
|
||||
this.currentCustomScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.customColorSchemes)
|
||||
this.currentStockScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.stockColorSchemes)
|
||||
this.allColorSchemes = this.customColorSchemes.concat(this.stockColorSchemes)
|
||||
this.changeDetector.markForCheck()
|
||||
}
|
||||
|
||||
editScheme () {
|
||||
this.editing = true
|
||||
}
|
||||
|
||||
saveScheme () {
|
||||
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== this.config.store.terminal.colorScheme.name)
|
||||
this.customColorSchemes.push(this.config.store.terminal.colorScheme)
|
||||
this.config.store.terminal.customColorSchemes = this.customColorSchemes
|
||||
this.config.save()
|
||||
this.cancelEditing()
|
||||
this.update()
|
||||
}
|
||||
|
||||
cancelEditing () {
|
||||
this.editing = false
|
||||
}
|
||||
|
||||
async deleteScheme (scheme: TerminalColorScheme) {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: this.translate.instant('Delete "{name}"?', scheme),
|
||||
buttons: [
|
||||
this.translate.instant('Delete'),
|
||||
this.translate.instant('Keep'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
},
|
||||
)).response === 0) {
|
||||
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== scheme.name)
|
||||
this.config.store.terminal.customColorSchemes = this.customColorSchemes
|
||||
this.config.save()
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentSchemeName () {
|
||||
return (this.currentCustomScheme ?? this.currentStockScheme)?.name ?? 'Custom'
|
||||
}
|
||||
|
||||
findMatchingScheme (scheme: TerminalColorScheme, schemes: TerminalColorScheme[]) {
|
||||
return schemes.find(x => deepEqual(x, scheme)) ?? null
|
||||
}
|
||||
|
||||
colorsTrackBy (index) {
|
||||
return index
|
||||
platform: PlatformService,
|
||||
) {
|
||||
this.defaultTab = platform.getTheme()
|
||||
}
|
||||
}
|
||||
|
@@ -33,10 +33,12 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
wordSeparator: ' ()[]{}\'"',
|
||||
colorScheme: {
|
||||
__nonStructural: true,
|
||||
selection: null,
|
||||
cursorAccent: null,
|
||||
...DefaultColorSchemes.defaultColorScheme,
|
||||
},
|
||||
lightColorScheme: {
|
||||
__nonStructural: true,
|
||||
...DefaultColorSchemes.defaultLightColorScheme,
|
||||
},
|
||||
customColorSchemes: [],
|
||||
warnOnMultilinePaste: true,
|
||||
searchRegexAlwaysEnabled: false,
|
||||
|
@@ -363,7 +363,7 @@ export class XTermFrontend extends Frontend {
|
||||
private configureColors (scheme: TerminalColorScheme|undefined): void {
|
||||
const config = this.configService.store
|
||||
|
||||
scheme = scheme ?? config.terminal.colorScheme
|
||||
scheme = scheme ?? this.themes._getActiveColorScheme()
|
||||
|
||||
const theme: ITheme = {
|
||||
foreground: scheme!.foreground,
|
||||
|
@@ -19,6 +19,7 @@ import { LoginScriptsSettingsComponent } from './components/loginScriptsSettings
|
||||
import { TerminalToolbarComponent } from './components/terminalToolbar.component'
|
||||
import { ColorSchemeSelectorComponent } from './components/colorSchemeSelector.component'
|
||||
import { InputProcessingSettingsComponent } from './components/inputProcessingSettings.component'
|
||||
import { ColorSchemeSettingsForModeComponent } from './components/colorSchemeSettingsForMode.component'
|
||||
|
||||
import { TerminalDecorator } from './api/decorator'
|
||||
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
|
||||
@@ -78,6 +79,7 @@ import { DefaultColorSchemes } from './colorSchemes'
|
||||
LoginScriptsSettingsComponent,
|
||||
TerminalToolbarComponent,
|
||||
InputProcessingSettingsComponent,
|
||||
ColorSchemeSettingsForModeComponent,
|
||||
],
|
||||
exports: [
|
||||
ColorPickerComponent,
|
||||
|
Reference in New Issue
Block a user