mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-24 04:17:59 +00:00
automatic dark/light theme - fixes #3934
This commit is contained in:
@@ -86,14 +86,18 @@ export interface FileUploadOptions {
|
|||||||
multiple: boolean
|
multiple: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PlatformTheme = 'light'|'dark'
|
||||||
|
|
||||||
export abstract class PlatformService {
|
export abstract class PlatformService {
|
||||||
supportsWindowControls = false
|
supportsWindowControls = false
|
||||||
|
|
||||||
get fileTransferStarted$ (): Observable<FileTransfer> { return this.fileTransferStarted }
|
get fileTransferStarted$ (): Observable<FileTransfer> { return this.fileTransferStarted }
|
||||||
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
|
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
|
||||||
|
get themeChanged$ (): Observable<PlatformTheme> { return this.themeChanged }
|
||||||
|
|
||||||
protected fileTransferStarted = new Subject<FileTransfer>()
|
protected fileTransferStarted = new Subject<FileTransfer>()
|
||||||
protected displayMetricsChanged = new Subject<void>()
|
protected displayMetricsChanged = new Subject<void>()
|
||||||
|
protected themeChanged = new Subject<PlatformTheme>()
|
||||||
|
|
||||||
abstract readClipboard (): string
|
abstract readClipboard (): string
|
||||||
abstract setClipboard (content: ClipboardContent): void
|
abstract setClipboard (content: ClipboardContent): void
|
||||||
@@ -169,6 +173,10 @@ export abstract class PlatformService {
|
|||||||
throw new Error('Not implemented')
|
throw new Error('Not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTheme (): PlatformTheme {
|
||||||
|
return 'dark'
|
||||||
|
}
|
||||||
|
|
||||||
abstract getOSRelease (): string
|
abstract getOSRelease (): string
|
||||||
abstract getAppVersion (): string
|
abstract getAppVersion (): string
|
||||||
abstract openExternal (url: string): void
|
abstract openExternal (url: string): void
|
||||||
|
@@ -11,7 +11,7 @@ appearance:
|
|||||||
tabsLocation: top
|
tabsLocation: top
|
||||||
tabsInFullscreen: false
|
tabsInFullscreen: false
|
||||||
cycleTabs: true
|
cycleTabs: true
|
||||||
theme: Standard
|
theme: Follow the color scheme
|
||||||
frame: thin
|
frame: thin
|
||||||
css: '/* * { color: blue !important; } */'
|
css: '/* * { color: blue !important; } */'
|
||||||
opacity: 1.0
|
opacity: 1.0
|
||||||
|
@@ -3,6 +3,7 @@ import { Subject, Observable } from 'rxjs'
|
|||||||
import * as Color from 'color'
|
import * as Color from 'color'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
import { Theme } from '../api/theme'
|
import { Theme } from '../api/theme'
|
||||||
|
import { PlatformService } from '../api/platform'
|
||||||
import { NewTheme } from '../theme'
|
import { NewTheme } from '../theme'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -17,6 +18,7 @@ export class ThemesService {
|
|||||||
private constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private standardTheme: NewTheme,
|
private standardTheme: NewTheme,
|
||||||
|
private platform: PlatformService,
|
||||||
@Inject(Theme) private themes: Theme[],
|
@Inject(Theme) private themes: Theme[],
|
||||||
) {
|
) {
|
||||||
this.rootElementStyleBackup = document.documentElement.style.cssText
|
this.rootElementStyleBackup = document.documentElement.style.cssText
|
||||||
@@ -24,6 +26,10 @@ export class ThemesService {
|
|||||||
config.ready$.toPromise().then(() => {
|
config.ready$.toPromise().then(() => {
|
||||||
this.applyCurrentTheme()
|
this.applyCurrentTheme()
|
||||||
this.applyThemeVariables()
|
this.applyThemeVariables()
|
||||||
|
platform.themeChanged$.subscribe(() => {
|
||||||
|
this.applyCurrentTheme()
|
||||||
|
this.applyThemeVariables()
|
||||||
|
})
|
||||||
config.changed$.subscribe(() => {
|
config.changed$.subscribe(() => {
|
||||||
this.applyCurrentTheme()
|
this.applyCurrentTheme()
|
||||||
this.applyThemeVariables()
|
this.applyThemeVariables()
|
||||||
@@ -36,7 +42,7 @@ export class ThemesService {
|
|||||||
document.documentElement.style.cssText = this.rootElementStyleBackup
|
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()
|
const isDark = Color(theme.background).luminosity() < Color(theme.foreground).luminosity()
|
||||||
|
|
||||||
function more (some, factor) {
|
function more (some, factor) {
|
||||||
@@ -106,8 +112,10 @@ export class ThemesService {
|
|||||||
|
|
||||||
const themeColors = {
|
const themeColors = {
|
||||||
primary: theme.colors[accentIndex],
|
primary: theme.colors[accentIndex],
|
||||||
secondary: less(theme.background, 0.5).string(),
|
secondary: isDark
|
||||||
tertiary: theme.colors[8],
|
? less(theme.background, 0.5).string()
|
||||||
|
: less(theme.background, 0.125).string(),
|
||||||
|
tertiary: more(theme.background, 0.75).string(),
|
||||||
warning: theme.colors[3],
|
warning: theme.colors[3],
|
||||||
danger: theme.colors[1],
|
danger: theme.colors[1],
|
||||||
success: theme.colors[2],
|
success: theme.colors[2],
|
||||||
@@ -184,6 +192,15 @@ export class ThemesService {
|
|||||||
return this.findTheme(this.config.store.appearance.theme) ?? this.standardTheme
|
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 {
|
applyTheme (theme: Theme): void {
|
||||||
if (!this.styleElement) {
|
if (!this.styleElement) {
|
||||||
this.styleElement = document.createElement('style')
|
this.styleElement = document.createElement('style')
|
||||||
|
@@ -110,7 +110,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.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-hover-color: var(--theme-fg-less);
|
||||||
--bs-nav-link-disabled-color: var(--bs-gray);
|
--bs-nav-link-disabled-color: var(--bs-gray);
|
||||||
}
|
}
|
||||||
@@ -119,8 +119,8 @@ body {
|
|||||||
--bs-nav-tabs-border-width: 2px;
|
--bs-nav-tabs-border-width: 2px;
|
||||||
--bs-nav-tabs-border-radius: 0;
|
--bs-nav-tabs-border-radius: 0;
|
||||||
--bs-nav-tabs-link-hover-border-color: var(--bs-body-bg);
|
--bs-nav-tabs-link-hover-border-color: var(--bs-body-bg);
|
||||||
--bs-nav-tabs-border-color: var(--theme-fg-less-2);
|
--bs-nav-tabs-border-color: var(--theme-fg);
|
||||||
--bs-nav-tabs-link-active-color: var(--theme-fg-less-2);
|
--bs-nav-tabs-link-active-color: var(--theme-fg);
|
||||||
|
|
||||||
--bs-nav-tabs-link-active-bg: transparent;
|
--bs-nav-tabs-link-active-bg: transparent;
|
||||||
--bs-nav-tabs-link-active-border-color: transparent;
|
--bs-nav-tabs-link-active-border-color: transparent;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
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'
|
import * as remote from '@electron/remote'
|
||||||
|
|
||||||
export interface MessageBoxResponse {
|
export interface MessageBoxResponse {
|
||||||
@@ -20,6 +20,7 @@ export class ElectronService {
|
|||||||
process: any
|
process: any
|
||||||
autoUpdater: AutoUpdater
|
autoUpdater: AutoUpdater
|
||||||
powerSaveBlocker: PowerSaveBlocker
|
powerSaveBlocker: PowerSaveBlocker
|
||||||
|
nativeTheme: NativeTheme
|
||||||
TouchBar: typeof TouchBar
|
TouchBar: typeof TouchBar
|
||||||
BrowserWindow: typeof BrowserWindow
|
BrowserWindow: typeof BrowserWindow
|
||||||
Menu: typeof Menu
|
Menu: typeof Menu
|
||||||
@@ -43,5 +44,7 @@ export class ElectronService {
|
|||||||
this.BrowserWindow = remote.BrowserWindow
|
this.BrowserWindow = remote.BrowserWindow
|
||||||
this.Menu = remote.Menu
|
this.Menu = remote.Menu
|
||||||
this.MenuItem = remote.MenuItem
|
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 { ElectronHostWindow } from './hostWindow.service'
|
||||||
import { ShellIntegrationService } from './shellIntegration.service'
|
import { ShellIntegrationService } from './shellIntegration.service'
|
||||||
import { ElectronHostAppService } from './hostApp.service'
|
import { ElectronHostAppService } from './hostApp.service'
|
||||||
|
import { PlatformTheme } from '../../../tabby-core/src/api/platform'
|
||||||
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
||||||
@@ -40,6 +41,10 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
electron.ipcRenderer.on('host:display-metrics-changed', () => {
|
electron.ipcRenderer.on('host:display-metrics-changed', () => {
|
||||||
this.zone.run(() => this.displayMetricsChanged.next())
|
this.zone.run(() => this.displayMetricsChanged.next())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
electron.nativeTheme.on('updated', () => {
|
||||||
|
this.zone.run(() => this.themeChanged.next(this.getTheme()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
readClipboard (): string {
|
readClipboard (): string {
|
||||||
@@ -243,6 +248,14 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
},
|
},
|
||||||
)).filePaths[0]
|
)).filePaths[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTheme (): PlatformTheme {
|
||||||
|
if (this.electron.nativeTheme.shouldUseDarkColors) {
|
||||||
|
return 'dark'
|
||||||
|
} else {
|
||||||
|
return 'light'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectronFileUpload extends FileUpload {
|
class ElectronFileUpload extends FileUpload {
|
||||||
|
@@ -331,6 +331,10 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
|
|||||||
this.frontend?.focus()
|
this.frontend?.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.subscribeUntilDestroyed(this.platform.themeChanged$, () => {
|
||||||
|
this.configure()
|
||||||
|
})
|
||||||
|
|
||||||
const cls: new (..._) => Frontend = {
|
const cls: new (..._) => Frontend = {
|
||||||
xterm: XTermFrontend,
|
xterm: XTermFrontend,
|
||||||
'xterm-webgl': XTermWebGLFrontend,
|
'xterm-webgl': XTermWebGLFrontend,
|
||||||
|
@@ -27,9 +27,41 @@ export class DefaultColorSchemes extends TerminalColorSchemeProvider {
|
|||||||
'#b7fff9',
|
'#b7fff9',
|
||||||
'#ffffff',
|
'#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[]> {
|
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) Color schemes
|
||||||
h3.mb-3(translate) Current color scheme
|
|
||||||
|
|
||||||
.d-flex.align-items-center(*ngIf='!editing')
|
ul.nav-tabs(ngbNav, #nav='ngbNav', [activeId]='defaultTab')
|
||||||
span {{getCurrentSchemeName()}}
|
li(ngbNavItem='dark')
|
||||||
.me-auto
|
a(ngbNavLink, translate) Dark mode
|
||||||
.btn-toolbar
|
ng-template(ngbNavContent)
|
||||||
button.btn.btn-secondary((click)='editScheme()')
|
color-scheme-settings-for-mode(configKey='colorScheme')
|
||||||
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
|
|
||||||
|
|
||||||
div(*ngIf='editing')
|
li(ngbNavItem='light')
|
||||||
.mb-3
|
a(ngbNavLink, translate) Light mode
|
||||||
label(translate) Name
|
ng-template(ngbNavContent)
|
||||||
input.form-control(type='text', [(ngModel)]='config.store.terminal.colorScheme.name')
|
color-scheme-settings-for-mode(configKey='lightColorScheme')
|
||||||
|
|
||||||
.mb-3
|
div([ngbNavOutlet]='nav')
|
||||||
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')
|
|
||||||
|
@@ -1,114 +1,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
import { Component } from '@angular/core'
|
||||||
import deepEqual from 'deep-equal'
|
import { PlatformService } from 'tabby-core'
|
||||||
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 */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './colorSchemeSettingsTab.component.pug',
|
templateUrl: './colorSchemeSettingsTab.component.pug',
|
||||||
styleUrls: ['./colorSchemeSettingsTab.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
})
|
||||||
export class ColorSchemeSettingsTabComponent {
|
export class ColorSchemeSettingsTabComponent {
|
||||||
@Input() stockColorSchemes: TerminalColorScheme[] = []
|
defaultTab = 'dark'
|
||||||
@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 (
|
constructor (
|
||||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
platform: PlatformService,
|
||||||
private changeDetector: ChangeDetectorRef,
|
) {
|
||||||
private platform: PlatformService,
|
this.defaultTab = platform.getTheme()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,10 +33,12 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
wordSeparator: ' ()[]{}\'"',
|
wordSeparator: ' ()[]{}\'"',
|
||||||
colorScheme: {
|
colorScheme: {
|
||||||
__nonStructural: true,
|
__nonStructural: true,
|
||||||
selection: null,
|
|
||||||
cursorAccent: null,
|
|
||||||
...DefaultColorSchemes.defaultColorScheme,
|
...DefaultColorSchemes.defaultColorScheme,
|
||||||
},
|
},
|
||||||
|
lightColorScheme: {
|
||||||
|
__nonStructural: true,
|
||||||
|
...DefaultColorSchemes.defaultLightColorScheme,
|
||||||
|
},
|
||||||
customColorSchemes: [],
|
customColorSchemes: [],
|
||||||
warnOnMultilinePaste: true,
|
warnOnMultilinePaste: true,
|
||||||
searchRegexAlwaysEnabled: false,
|
searchRegexAlwaysEnabled: false,
|
||||||
|
@@ -363,7 +363,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
private configureColors (scheme: TerminalColorScheme|undefined): void {
|
private configureColors (scheme: TerminalColorScheme|undefined): void {
|
||||||
const config = this.configService.store
|
const config = this.configService.store
|
||||||
|
|
||||||
scheme = scheme ?? config.terminal.colorScheme
|
scheme = scheme ?? this.themes._getActiveColorScheme()
|
||||||
|
|
||||||
const theme: ITheme = {
|
const theme: ITheme = {
|
||||||
foreground: scheme!.foreground,
|
foreground: scheme!.foreground,
|
||||||
|
@@ -19,6 +19,7 @@ import { LoginScriptsSettingsComponent } from './components/loginScriptsSettings
|
|||||||
import { TerminalToolbarComponent } from './components/terminalToolbar.component'
|
import { TerminalToolbarComponent } from './components/terminalToolbar.component'
|
||||||
import { ColorSchemeSelectorComponent } from './components/colorSchemeSelector.component'
|
import { ColorSchemeSelectorComponent } from './components/colorSchemeSelector.component'
|
||||||
import { InputProcessingSettingsComponent } from './components/inputProcessingSettings.component'
|
import { InputProcessingSettingsComponent } from './components/inputProcessingSettings.component'
|
||||||
|
import { ColorSchemeSettingsForModeComponent } from './components/colorSchemeSettingsForMode.component'
|
||||||
|
|
||||||
import { TerminalDecorator } from './api/decorator'
|
import { TerminalDecorator } from './api/decorator'
|
||||||
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
|
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
|
||||||
@@ -78,6 +79,7 @@ import { DefaultColorSchemes } from './colorSchemes'
|
|||||||
LoginScriptsSettingsComponent,
|
LoginScriptsSettingsComponent,
|
||||||
TerminalToolbarComponent,
|
TerminalToolbarComponent,
|
||||||
InputProcessingSettingsComponent,
|
InputProcessingSettingsComponent,
|
||||||
|
ColorSchemeSettingsForModeComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ColorPickerComponent,
|
ColorPickerComponent,
|
||||||
|
Reference in New Issue
Block a user