1
1
import { FastAverageColor } from 'fast-average-color' ;
2
+ import Color from 'color' ;
2
3
3
4
import style from './style.css?inline' ;
4
5
5
6
import { createPlugin } from '@/utils' ;
6
7
import { t } from '@/i18n' ;
7
8
8
- import type { VideoDataChanged } from '@/types/video-data-changed' ;
9
+ const COLOR_KEY = '--ytmusic-album-color' ;
10
+ const DARK_COLOR_KEY = '--ytmusic-album-color-dark' ;
9
11
10
12
export default createPlugin ( {
11
13
name : ( ) => t ( 'plugins.album-color-theme.name' ) ,
@@ -16,69 +18,8 @@ export default createPlugin({
16
18
} ,
17
19
stylesheets : [ style ] ,
18
20
renderer : {
19
- hexToHSL : ( H : string ) => {
20
- // Convert hex to RGB first
21
- let r = 0 ;
22
- let g = 0 ;
23
- let b = 0 ;
24
- if ( H . length == 4 ) {
25
- r = Number ( '0x' + H [ 1 ] + H [ 1 ] ) ;
26
- g = Number ( '0x' + H [ 2 ] + H [ 2 ] ) ;
27
- b = Number ( '0x' + H [ 3 ] + H [ 3 ] ) ;
28
- } else if ( H . length == 7 ) {
29
- r = Number ( '0x' + H [ 1 ] + H [ 2 ] ) ;
30
- g = Number ( '0x' + H [ 3 ] + H [ 4 ] ) ;
31
- b = Number ( '0x' + H [ 5 ] + H [ 6 ] ) ;
32
- }
33
- // Then to HSL
34
- r /= 255 ;
35
- g /= 255 ;
36
- b /= 255 ;
37
- const cmin = Math . min ( r , g , b ) ;
38
- const cmax = Math . max ( r , g , b ) ;
39
- const delta = cmax - cmin ;
40
- let h : number ;
41
- let s : number ;
42
- let l : number ;
43
-
44
- if ( delta == 0 ) {
45
- h = 0 ;
46
- } else if ( cmax == r ) {
47
- h = ( ( g - b ) / delta ) % 6 ;
48
- } else if ( cmax == g ) {
49
- h = ( ( b - r ) / delta ) + 2 ;
50
- } else {
51
- h = ( ( r - g ) / delta ) + 4 ;
52
- }
53
-
54
- h = Math . round ( h * 60 ) ;
55
-
56
- if ( h < 0 ) {
57
- h += 360 ;
58
- }
59
-
60
- l = ( cmax + cmin ) / 2 ;
61
- s = delta == 0 ? 0 : delta / ( 1 - Math . abs ( ( 2 * l ) - 1 ) ) ;
62
- s = + ( s * 100 ) . toFixed ( 1 ) ;
63
- l = + ( l * 100 ) . toFixed ( 1 ) ;
64
-
65
- //return "hsl(" + h + "," + s + "%," + l + "%)";
66
- return [ h , s , l ] ;
67
- } ,
68
- hue : 0 ,
69
- saturation : 0 ,
70
- lightness : 0 ,
71
-
72
- changeElementColor : (
73
- element : HTMLElement | null ,
74
- hue : number ,
75
- saturation : number ,
76
- lightness : number ,
77
- ) => {
78
- if ( element ) {
79
- element . style . backgroundColor = `hsl(${ hue } , ${ saturation } %, ${ lightness } %)` ;
80
- }
81
- } ,
21
+ color : null as Color | null ,
22
+ darkColor : null as Color | null ,
82
23
83
24
playerPage : null as HTMLElement | null ,
84
25
navBarBackground : null as HTMLElement | null ,
@@ -103,113 +44,66 @@ export default createPlugin({
103
44
'#mini-guide-background' ,
104
45
) ;
105
46
this . ytmusicAppLayout = document . querySelector < HTMLElement > ( '#layout' ) ;
47
+ } ,
48
+ onPlayerApiReady ( playerApi ) {
49
+ const fastAverageColor = new FastAverageColor ( ) ;
50
+
51
+ document . addEventListener ( 'videodatachange' , async ( event ) => {
52
+ if ( event . detail . name !== 'dataloaded' ) return ;
53
+
54
+ const playerResponse = playerApi . getPlayerResponse ( ) ;
55
+ const thumbnail = playerResponse ?. videoDetails ?. thumbnail ?. thumbnails ?. at ( 0 ) ;
56
+ if ( ! thumbnail ) return ;
106
57
107
- const observer = new MutationObserver ( ( mutationsList ) => {
108
- for ( const mutation of mutationsList ) {
109
- if ( mutation . type === 'attributes' ) {
110
- const isPageOpen =
111
- this . ytmusicAppLayout ?. hasAttribute ( 'player-page-open' ) ;
112
- if ( isPageOpen ) {
113
- this . changeElementColor (
114
- this . sidebarSmall ,
115
- this . hue ,
116
- this . saturation ,
117
- this . lightness - 30 ,
118
- ) ;
119
- } else {
120
- if ( this . sidebarSmall ) {
121
- this . sidebarSmall . style . backgroundColor = 'black' ;
122
- }
123
- }
58
+ const albumColor = await fastAverageColor . getColorAsync ( thumbnail . url )
59
+ . catch ( ( err ) => {
60
+ console . error ( err ) ;
61
+ return null ;
62
+ } ) ;
63
+
64
+ if ( albumColor ) {
65
+ const target = Color ( albumColor . hex ) ;
66
+
67
+ this . darkColor = target . darken ( 0.3 ) . rgb ( ) ;
68
+ this . color = target . darken ( 0.15 ) . rgb ( ) ;
69
+
70
+ while ( this . color . luminosity ( ) > 0.5 ) {
71
+ this . color = this . color ?. darken ( 0.05 ) ;
72
+ this . darkColor = this . darkColor ?. darken ( 0.05 ) ;
124
73
}
74
+
75
+ document . documentElement . style . setProperty ( COLOR_KEY , `${ ~ ~ this . color . red ( ) } , ${ ~ ~ this . color . green ( ) } , ${ ~ ~ this . color . blue ( ) } ` ) ;
76
+ document . documentElement . style . setProperty ( DARK_COLOR_KEY , `${ ~ ~ this . darkColor . red ( ) } , ${ ~ ~ this . darkColor . green ( ) } , ${ ~ ~ this . darkColor . blue ( ) } ` ) ;
77
+ } else {
78
+ document . documentElement . style . setProperty ( COLOR_KEY , '0, 0, 0' ) ;
79
+ document . documentElement . style . setProperty ( DARK_COLOR_KEY , '0, 0, 0' ) ;
125
80
}
81
+
82
+ this . updateColor ( ) ;
126
83
} ) ;
84
+ } ,
85
+ getColor ( key : string , alpha = 1 ) {
86
+ return `rgba(var(${ key } ), ${ alpha } )` ;
87
+ } ,
88
+ updateColor ( ) {
89
+ const change = ( element : HTMLElement | null , color : string ) => {
90
+ if ( element ) {
91
+ element . style . backgroundColor = color ;
92
+ }
93
+ } ;
94
+
95
+ change ( this . playerPage , this . getColor ( DARK_COLOR_KEY ) ) ;
96
+ change ( this . navBarBackground , this . getColor ( COLOR_KEY ) ) ;
97
+ change ( this . ytmusicPlayerBar , this . getColor ( COLOR_KEY ) ) ;
98
+ change ( this . playerBarBackground , this . getColor ( COLOR_KEY ) ) ;
99
+ change ( this . sidebarBig , this . getColor ( COLOR_KEY ) ) ;
127
100
128
- if ( this . playerPage ) {
129
- observer . observe ( this . playerPage , { attributes : true } ) ;
101
+ if ( this . ytmusicAppLayout ?. hasAttribute ( 'player-page-open' ) ) {
102
+ change ( this . sidebarSmall , this . getColor ( DARK_COLOR_KEY ) ) ;
130
103
}
131
- } ,
132
- onPlayerApiReady ( playerApi ) {
133
- const fastAverageColor = new FastAverageColor ( ) ;
134
104
135
- document . addEventListener (
136
- 'videodatachange' ,
137
- ( event : CustomEvent < VideoDataChanged > ) => {
138
- if ( event . detail . name === 'dataloaded' ) {
139
- const playerResponse = playerApi . getPlayerResponse ( ) ;
140
- const thumbnail =
141
- playerResponse ?. videoDetails ?. thumbnail ?. thumbnails ?. at ( 0 ) ;
142
- if ( thumbnail ) {
143
- fastAverageColor
144
- . getColorAsync ( thumbnail . url )
145
- . then ( ( albumColor ) => {
146
- if ( albumColor ) {
147
- const [ hue , saturation , lightness ] = ( [
148
- this . hue ,
149
- this . saturation ,
150
- this . lightness ,
151
- ] = this . hexToHSL ( albumColor . hex ) ) ;
152
- this . changeElementColor (
153
- this . playerPage ,
154
- hue ,
155
- saturation ,
156
- lightness - 30 ,
157
- ) ;
158
- this . changeElementColor (
159
- this . navBarBackground ,
160
- hue ,
161
- saturation ,
162
- lightness - 15 ,
163
- ) ;
164
- this . changeElementColor (
165
- this . ytmusicPlayerBar ,
166
- hue ,
167
- saturation ,
168
- lightness - 15 ,
169
- ) ;
170
- this . changeElementColor (
171
- this . playerBarBackground ,
172
- hue ,
173
- saturation ,
174
- lightness - 15 ,
175
- ) ;
176
- this . changeElementColor (
177
- this . sidebarBig ,
178
- hue ,
179
- saturation ,
180
- lightness - 15 ,
181
- ) ;
182
- if (
183
- this . ytmusicAppLayout ?. hasAttribute ( 'player-page-open' )
184
- ) {
185
- this . changeElementColor (
186
- this . sidebarSmall ,
187
- hue ,
188
- saturation ,
189
- lightness - 30 ,
190
- ) ;
191
- }
192
- const ytRightClickList =
193
- document . querySelector < HTMLElement > (
194
- 'tp-yt-paper-listbox' ,
195
- ) ;
196
- this . changeElementColor (
197
- ytRightClickList ,
198
- hue ,
199
- saturation ,
200
- lightness - 15 ,
201
- ) ;
202
- } else {
203
- if ( this . playerPage ) {
204
- this . playerPage . style . backgroundColor = '#000000' ;
205
- }
206
- }
207
- } )
208
- . catch ( ( e ) => console . error ( e ) ) ;
209
- }
210
- }
211
- } ,
212
- ) ;
105
+ const ytRightClickList = document . querySelector < HTMLElement > ( 'tp-yt-paper-listbox' ) ;
106
+ change ( ytRightClickList , this . getColor ( COLOR_KEY ) ) ;
213
107
} ,
214
108
} ,
215
109
} ) ;
0 commit comments