1+ /*global document window*/
12import type GraphContainer from '@gitkraken/gitkraken-components' ;
2- import type { GraphRow , GraphZoneType } from '@gitkraken/gitkraken-components' ;
3+ import type { CssVariables , GraphRow , GraphZoneType } from '@gitkraken/gitkraken-components' ;
34import { consume } from '@lit/context' ;
45import { SignalWatcher } from '@lit-labs/signals' ;
56import { html , LitElement } from 'lit' ;
6- import { customElement , query } from 'lit/decorators.js' ;
7+ import { customElement , query , state } from 'lit/decorators.js' ;
78import { ifDefined } from 'lit/directives/if-defined.js' ;
89import type { GitGraphRowType } from '../../../../../git/models/graph' ;
910import { filterMap } from '../../../../../system/array' ;
11+ import { getCssMixedColorValue , getCssOpacityColorValue , getCssVariable } from '../../../../../system/color' ;
12+ import { debounce } from '../../../../../system/function/debounce' ;
1013import {
1114 DoubleClickedCommandType ,
1215 GetMissingAvatarsCommand ,
@@ -20,10 +23,28 @@ import type { CustomEventType } from '../../../shared/components/element';
2023import { ipcContext } from '../../../shared/contexts/ipc' ;
2124import type { TelemetryContext } from '../../../shared/contexts/telemetry' ;
2225import { telemetryContext } from '../../../shared/contexts/telemetry' ;
26+ import type { Disposable } from '../../../shared/events' ;
27+ import type { ThemeChangeEvent } from '../../../shared/theme' ;
28+ import { onDidChangeTheme } from '../../../shared/theme' ;
2329import { stateContext } from '../context' ;
2430import { graphStateContext } from '../stateProvider' ;
25- import type { GlGraph } from './graph-wrapper-element' ;
26- import './graph-wrapper-element' ;
31+ import type { GlGraph } from './gl-graph' ;
32+ import type { GraphWrapperTheming } from './gl-graph.react' ;
33+ import './gl-graph' ;
34+
35+ // These properties in the DOM are auto-generated by VS Code from our `contributes.colors` in package.json
36+ const graphLaneThemeColors = new Map ( [
37+ [ '--vscode-gitlens-graphLane1Color' , '#15a0bf' ] ,
38+ [ '--vscode-gitlens-graphLane2Color' , '#0669f7' ] ,
39+ [ '--vscode-gitlens-graphLane3Color' , '#8e00c2' ] ,
40+ [ '--vscode-gitlens-graphLane4Color' , '#c517b6' ] ,
41+ [ '--vscode-gitlens-graphLane5Color' , '#d90171' ] ,
42+ [ '--vscode-gitlens-graphLane6Color' , '#cd0101' ] ,
43+ [ '--vscode-gitlens-graphLane7Color' , '#f25d2e' ] ,
44+ [ '--vscode-gitlens-graphLane8Color' , '#f2ca33' ] ,
45+ [ '--vscode-gitlens-graphLane9Color' , '#7bd938' ] ,
46+ [ '--vscode-gitlens-graphLane10Color' , '#2ece9d' ] ,
47+ ] ) ;
2748
2849declare global {
2950 // interface HTMLElementTagNameMap {
@@ -57,6 +78,8 @@ export class GlGraphWrapper extends SignalWatcher(LitElement) {
5778 return this ;
5879 }
5980
81+ private disposables : Disposable [ ] = [ ] ;
82+
6083 @consume ( { context : graphStateContext } )
6184 private readonly graphAppState ! : typeof graphStateContext . __context__ ;
6285
@@ -77,6 +100,29 @@ export class GlGraphWrapper extends SignalWatcher(LitElement) {
77100 this . ref = ref ;
78101 } ;
79102
103+ @state ( )
104+ private theming ?: GraphWrapperTheming ;
105+
106+ override connectedCallback ( ) : void {
107+ super . connectedCallback ?.( ) ;
108+
109+ this . theming = this . getGraphTheming ( ) ;
110+ this . disposables . push (
111+ onDidChangeTheme (
112+ debounce ( ( e : ThemeChangeEvent ) => {
113+ this . theming = this . getGraphTheming ( e ) ;
114+ } , 100 ) ,
115+ ) ,
116+ ) ;
117+ }
118+
119+ override disconnectedCallback ( ) : void {
120+ super . disconnectedCallback ?.( ) ;
121+
122+ this . disposables . forEach ( d => d . dispose ( ) ) ;
123+ this . disposables = [ ] ;
124+ }
125+
80126 override render ( ) {
81127 const { graphAppState, hostState } = this ;
82128
@@ -100,7 +146,7 @@ export class GlGraphWrapper extends SignalWatcher(LitElement) {
100146 .rowsStats=${ hostState . rowsStats }
101147 .searchResults=${ graphAppState . searchResults }
102148 .selectedRows=${ graphAppState . selectedRows }
103- .theming=${ graphAppState . theming }
149+ .theming=${ this . theming }
104150 ?windowFocused=${ hostState . windowFocused }
105151 .workingTreeStats=${ hostState . workingTreeStats }
106152 @changecolumns=${ this . onColumnsChanged }
@@ -193,4 +239,126 @@ export class GlGraphWrapper extends SignalWatcher(LitElement) {
193239 private onVisibleDaysChanged ( { detail } : CustomEventType < 'graph-changevisibledays' > ) {
194240 this . dispatchEvent ( new CustomEvent ( 'gl-graph-change-visible-days' , { detail : detail } ) ) ;
195241 }
242+
243+ private readonly themingDefaults : { cssVariables : CssVariables ; themeOpacityFactor : number } = {
244+ cssVariables : ( ( ) => {
245+ const bgColor = getCssVariableValue ( '--color-background' ) ;
246+ const mixedGraphColors : CssVariables = { } ;
247+ let i = 0 ;
248+ let color ;
249+ for ( const [ colorVar , colorDefault ] of graphLaneThemeColors ) {
250+ color = getCssVariableValue ( colorVar , { fallbackValue : colorDefault } ) ;
251+ mixedGraphColors [ `--graph-color-${ i } ` ] = color ;
252+ for ( const mixInt of [ 15 , 25 , 45 , 50 ] ) {
253+ mixedGraphColors [ `--graph-color-${ i } -bg${ mixInt } ` ] = getCssMixedColorValue ( bgColor , color , mixInt ) ;
254+ }
255+ for ( const mixInt of [ 10 , 50 ] ) {
256+ mixedGraphColors [ `--graph-color-${ i } -f${ mixInt } ` ] = getCssOpacityColorValue ( color , mixInt ) ;
257+ }
258+ i ++ ;
259+ }
260+ return {
261+ '--app__bg0' : getCssVariableValue ( '--color-background' ) ,
262+ '--panel__bg0' : getCssVariableValue ( '--color-graph-background' ) ,
263+ '--panel__bg1' : getCssVariableValue ( '--color-graph-background2' ) ,
264+ '--section-border' : getCssVariableValue ( '--color-graph-background2' ) ,
265+ '--selected-row' : getCssVariableValue ( '--color-graph-selected-row' ) ,
266+ '--selected-row-border' : 'none' ,
267+ '--hover-row' : getCssVariableValue ( '--color-graph-hover-row' ) ,
268+ '--hover-row-border' : 'none' ,
269+ '--scrollable-scrollbar-thickness' : getCssVariableValue ( '--graph-column-scrollbar-thickness' ) ,
270+ '--scroll-thumb-bg' : getCssVariableValue ( '--vscode-scrollbarSlider-background' ) ,
271+ '--scroll-marker-head-color' : getCssVariableValue ( '--color-graph-scroll-marker-head' ) ,
272+ '--scroll-marker-upstream-color' : getCssVariableValue ( '--color-graph-scroll-marker-upstream' ) ,
273+ '--scroll-marker-highlights-color' : getCssVariableValue ( '--color-graph-scroll-marker-highlights' ) ,
274+ '--scroll-marker-local-branches-color' : getCssVariableValue (
275+ '--color-graph-scroll-marker-local-branches' ,
276+ ) ,
277+ '--scroll-marker-remote-branches-color' : getCssVariableValue (
278+ '--color-graph-scroll-marker-remote-branches' ,
279+ ) ,
280+ '--scroll-marker-stashes-color' : getCssVariableValue ( '--color-graph-scroll-marker-stashes' ) ,
281+ '--scroll-marker-tags-color' : getCssVariableValue ( '--color-graph-scroll-marker-tags' ) ,
282+ '--scroll-marker-selection-color' : getCssVariableValue ( '--color-graph-scroll-marker-selection' ) ,
283+ '--scroll-marker-pull-requests-color' : getCssVariableValue ( '--color-graph-scroll-marker-pull-requests' ) ,
284+ '--stats-added-color' : getCssVariableValue ( '--color-graph-stats-added' ) ,
285+ '--stats-deleted-color' : getCssVariableValue ( '--color-graph-stats-deleted' ) ,
286+ '--stats-files-color' : getCssVariableValue ( '--color-graph-stats-files' ) ,
287+ '--stats-bar-border-radius' : getCssVariableValue ( '--graph-stats-bar-border-radius' ) ,
288+ '--stats-bar-height' : getCssVariableValue ( '--graph-stats-bar-height' ) ,
289+ '--text-selected' : getCssVariableValue ( '--color-graph-text-selected' ) ,
290+ '--text-selected-row' : getCssVariableValue ( '--color-graph-text-selected-row' ) ,
291+ '--text-hovered' : getCssVariableValue ( '--color-graph-text-hovered' ) ,
292+ '--text-dimmed-selected' : getCssVariableValue ( '--color-graph-text-dimmed-selected' ) ,
293+ '--text-dimmed' : getCssVariableValue ( '--color-graph-text-dimmed' ) ,
294+ '--text-normal' : getCssVariableValue ( '--color-graph-text-normal' ) ,
295+ '--text-secondary' : getCssVariableValue ( '--color-graph-text-secondary' ) ,
296+ '--text-disabled' : getCssVariableValue ( '--color-graph-text-disabled' ) ,
297+ '--text-accent' : getCssVariableValue ( '--color-link-foreground' ) ,
298+ '--text-inverse' : getCssVariableValue ( '--vscode-input-background' ) ,
299+ '--text-bright' : getCssVariableValue ( '--vscode-input-background' ) ,
300+ ...mixedGraphColors ,
301+ } ;
302+ } ) ( ) ,
303+ themeOpacityFactor : 1 ,
304+ } ;
305+
306+ private getGraphTheming ( e ?: ThemeChangeEvent ) : GraphWrapperTheming {
307+ // this will be called on theme updated as well as on config updated since it is dependent on the column colors from config changes and the background color from the theme
308+ const computedStyle = e ?. computedStyle ?? window . getComputedStyle ( document . documentElement ) ;
309+ const bgColor = getCssVariableValue ( '--color-background' , { computedStyle : computedStyle } ) ;
310+
311+ const mixedGraphColors : CssVariables = { } ;
312+
313+ let i = 0 ;
314+ let color ;
315+ for ( const [ colorVar , colorDefault ] of graphLaneThemeColors ) {
316+ color = getCssVariableValue ( colorVar , { computedStyle : computedStyle , fallbackValue : colorDefault } ) ;
317+
318+ mixedGraphColors [ `--column-${ i } -color` ] = getCssVariable ( colorVar , computedStyle ) || colorDefault ;
319+
320+ for ( const mixInt of [ 15 , 25 , 45 , 50 ] ) {
321+ mixedGraphColors [ `--graph-color-${ i } -bg${ mixInt } ` ] = getCssMixedColorValue ( bgColor , color , mixInt ) ;
322+ }
323+
324+ i ++ ;
325+ }
326+
327+ const isHighContrastTheme =
328+ e ?. isHighContrastTheme ??
329+ ( document . body . classList . contains ( 'vscode-high-contrast' ) ||
330+ document . body . classList . contains ( 'vscode-high-contrast-light' ) ) ;
331+
332+ return {
333+ cssVariables : {
334+ ...this . themingDefaults . cssVariables ,
335+ '--selected-row-border' : isHighContrastTheme
336+ ? `1px solid ${ getCssVariableValue ( '--color-graph-contrast-border' , { computedStyle : computedStyle } ) } `
337+ : 'none' ,
338+ '--hover-row-border' : isHighContrastTheme
339+ ? `1px dashed ${ getCssVariableValue ( '--color-graph-contrast-border' , { computedStyle : computedStyle } ) } `
340+ : 'none' ,
341+ ...mixedGraphColors ,
342+ } ,
343+ themeOpacityFactor :
344+ parseInt ( getCssVariable ( '--graph-theme-opacity-factor' , computedStyle ) ) ||
345+ this . themingDefaults . themeOpacityFactor ,
346+ } ;
347+ }
348+ }
349+
350+ function getCssVariableValue (
351+ variable : string ,
352+ options ?: { computedStyle ?: CSSStyleDeclaration ; fallbackValue ?: string } ,
353+ ) : string {
354+ const fallbackValue = options ?. computedStyle
355+ ? getCssVariable ( variable , options ?. computedStyle )
356+ : options ?. fallbackValue
357+ ? options . fallbackValue
358+ : undefined ;
359+
360+ if ( fallbackValue ) {
361+ return `var(${ variable } , ${ fallbackValue } )` ;
362+ }
363+ return `var(${ variable } )` ;
196364}
0 commit comments