github.com/oam-dev/kubevela@v1.9.11/references/cli/top/config/color.go (about) 1 /* 2 Copyright 2022 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package config 18 19 import ( 20 "embed" 21 "fmt" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "github.com/gdamore/tcell/v2" 27 "gopkg.in/yaml.v3" 28 ) 29 30 // Color is a color string. 31 type Color string 32 33 // ThemeConfig is the theme config. 34 type ThemeConfig struct { 35 Info struct { 36 Title Color `yaml:"title"` 37 Text Color `yaml:"text"` 38 } `yaml:"info"` 39 Menu struct { 40 Description Color `yaml:"description"` 41 Key Color `yaml:"key"` 42 } `yaml:"menu"` 43 Logo struct { 44 Text Color `yaml:"text"` 45 } `yaml:"logo"` 46 Crumbs struct { 47 Foreground Color `yaml:"foreground"` 48 Background Color `yaml:"background"` 49 } `yaml:"crumbs"` 50 Border struct { 51 App Color `yaml:"app"` 52 Table Color `yaml:"table"` 53 } `yaml:"border"` 54 Table struct { 55 Title Color `yaml:"title"` 56 Header Color `yaml:"header"` 57 Body Color `yaml:"body"` 58 CursorBg Color `yaml:"cursorbg"` 59 CursorFg Color `yaml:"cursorfg"` 60 } `yaml:"table"` 61 Status struct { 62 Starting Color `yaml:"starting"` 63 Healthy Color `yaml:"healthy"` 64 UnHealthy Color `yaml:"unhealthy"` 65 Waiting Color `yaml:"waiting"` 66 Succeeded Color `yaml:"succeeded"` 67 Failed Color `yaml:"failed"` 68 Unknown Color `yaml:"unknown"` 69 } `yaml:"status"` 70 Yaml struct { 71 Key Color `yaml:"key"` 72 Colon Color `yaml:"colon"` 73 Value Color `yaml:"value"` 74 } `yaml:"yaml"` 75 Topology struct { 76 Line Color `yaml:"line"` 77 App Color `yaml:"app"` 78 Workflow Color `yaml:"workflow"` 79 Component Color `yaml:"component"` 80 Policy Color `yaml:"policy"` 81 Trait Color `yaml:"trait"` 82 Kind Color `yaml:"kind"` 83 } `yaml:"topology"` 84 } 85 86 var ( 87 // ThemeConfigFS is the theme config file system. 88 //go:embed theme/* 89 ThemeConfigFS embed.FS 90 // ThemeMap is the theme map. 91 ThemeMap = make(map[string]ThemeConfig) 92 // ThemeNameArray is the theme name array. 93 ThemeNameArray []string 94 // homePath is the home path. 95 homePath string 96 // diyThemeDirPath is the diy theme dir path like ~/.vela/theme/themes 97 diyThemeDirPath string 98 // themeConfigFilePath is the theme config file path like ~/.vela/theme/_config.yaml 99 themeConfigFilePath string 100 ) 101 102 const ( 103 // DefaultColor represents a default color. 104 DefaultColor Color = "default" 105 // DefaultTheme represents a default theme. 106 DefaultTheme = "default" 107 embedThemePath = "theme" 108 themeHomeDirPath = ".vela/theme" 109 diyThemeDir = "themes" 110 themeConfigFile = "_config.yaml" 111 ) 112 113 func init() { 114 homePath, _ = os.UserHomeDir() 115 diyThemeDirPath = filepath.Join(homePath, themeHomeDirPath, diyThemeDir) 116 themeConfigFilePath = filepath.Join(homePath, themeHomeDirPath, themeConfigFile) 117 118 dir, err := ThemeConfigFS.ReadDir(embedThemePath) 119 if err != nil { 120 return 121 } 122 123 // embed theme config 124 for _, item := range dir { 125 content, err := ThemeConfigFS.ReadFile(filepath.Join(embedThemePath, item.Name())) 126 if err != nil { 127 continue 128 } 129 var t ThemeConfig 130 err = yaml.Unmarshal(content, &t) 131 if err != nil { 132 continue 133 } 134 themeName := strings.Split(item.Name(), ".")[0] 135 ThemeMap[themeName] = t 136 ThemeNameArray = append(ThemeNameArray, themeName) 137 } 138 139 // load diy theme config 140 dir, err = os.ReadDir(diyThemeDirPath) 141 if err != nil { 142 return 143 } 144 for _, item := range dir { 145 content, err := os.ReadFile(filepath.Clean(filepath.Join(diyThemeDirPath, item.Name()))) 146 if err != nil { 147 continue 148 } 149 var t ThemeConfig 150 err = yaml.Unmarshal(content, &t) 151 if err != nil { 152 continue 153 } 154 themeName := strings.Split(item.Name(), ".")[0] 155 ThemeMap[themeName] = t 156 ThemeNameArray = append(ThemeNameArray, themeName) 157 } 158 } 159 160 // LoadThemeConfig loads theme config from env or use the default setting 161 func LoadThemeConfig() *ThemeConfig { 162 themeConfigName := struct { 163 Name string `yaml:"name"` 164 }{} 165 // returns default theme if config file not exist 166 if !makeThemeConfigFileIfNotExist() { 167 return defaultTheme() 168 } 169 170 content, err := os.ReadFile(filepath.Clean(themeConfigFilePath)) 171 if err != nil { 172 return defaultTheme() 173 } 174 err = yaml.Unmarshal(content, &themeConfigName) 175 if err != nil { 176 return defaultTheme() 177 } 178 if themeConfigName.Name == DefaultTheme { 179 return defaultTheme() 180 } 181 182 if config, ok := ThemeMap[themeConfigName.Name]; ok { 183 return &config 184 } 185 return defaultTheme() 186 } 187 188 func defaultTheme() *ThemeConfig { 189 return &ThemeConfig{ 190 Info: struct { 191 Title Color `yaml:"title"` 192 Text Color `yaml:"text"` 193 }{ 194 Title: "royalblue", 195 Text: "lightgray", 196 }, 197 Menu: struct { 198 Description Color `yaml:"description"` 199 Key Color `yaml:"key"` 200 }{ 201 Description: "gray", 202 Key: "royalblue", 203 }, 204 Logo: struct { 205 Text Color `yaml:"text"` 206 }{ 207 Text: "royalblue", 208 }, 209 Crumbs: struct { 210 Foreground Color `yaml:"foreground"` 211 Background Color `yaml:"background"` 212 }{ 213 Foreground: "white", 214 Background: "royalblue", 215 }, 216 Border: struct { 217 App Color `yaml:"app"` 218 Table Color `yaml:"table"` 219 }{ 220 App: "black", 221 Table: "lightgray", 222 }, 223 Table: struct { 224 Title Color `yaml:"title"` 225 Header Color `yaml:"header"` 226 Body Color `yaml:"body"` 227 CursorBg Color `yaml:"cursorbg"` 228 CursorFg Color `yaml:"cursorfg"` 229 }{ 230 Title: "royalblue", 231 Header: "white", 232 Body: "blue", 233 CursorBg: "blue", 234 CursorFg: "black", 235 }, 236 Yaml: struct { 237 Key Color `yaml:"key"` 238 Colon Color `yaml:"colon"` 239 Value Color `yaml:"value"` 240 }{ 241 Key: "#d33582", 242 Colon: "lightgray", 243 Value: "#839495", 244 }, 245 Status: struct { 246 Starting Color `yaml:"starting"` 247 Healthy Color `yaml:"healthy"` 248 UnHealthy Color `yaml:"unhealthy"` 249 Waiting Color `yaml:"waiting"` 250 Succeeded Color `yaml:"succeeded"` 251 Failed Color `yaml:"failed"` 252 Unknown Color `yaml:"unknown"` 253 }{ 254 Starting: "blue", 255 Healthy: "green", 256 UnHealthy: "red", 257 Waiting: "yellow", 258 Succeeded: "orange", 259 Failed: "purple", 260 Unknown: "gray", 261 }, 262 Topology: struct { 263 Line Color `yaml:"line"` 264 App Color `yaml:"app"` 265 Workflow Color `yaml:"workflow"` 266 Component Color `yaml:"component"` 267 Policy Color `yaml:"policy"` 268 Trait Color `yaml:"trait"` 269 Kind Color `yaml:"kind"` 270 }{ 271 Line: "cadetblue", 272 App: "red", 273 Workflow: "orange", 274 Component: "green", 275 Policy: "yellow", 276 Trait: "lightseagreen", 277 Kind: "orange", 278 }, 279 } 280 } 281 282 // PersistentThemeConfig saves theme config to file 283 func PersistentThemeConfig(themeName string) { 284 makeThemeConfigFileIfNotExist() 285 _ = os.WriteFile(themeConfigFilePath, []byte("name : "+themeName), 0600) 286 } 287 288 // makeThemeConfigFileIfNotExist makes theme config file and write default content if not exist 289 func makeThemeConfigFileIfNotExist() bool { 290 velaThemeHome := filepath.Clean(filepath.Join(homePath, themeHomeDirPath)) 291 if _, err := os.Open(filepath.Clean(themeConfigFilePath)); err != nil { 292 if os.IsNotExist(err) { 293 // make file if not exist 294 _ = os.MkdirAll(filepath.Clean(velaThemeHome), 0700) 295 _ = os.WriteFile(filepath.Clean(themeConfigFilePath), []byte("name : "+DefaultTheme), 0600) 296 } 297 return false 298 } 299 return true 300 } 301 302 // String returns color as string. 303 func (c Color) String() string { 304 if c.isHex() { 305 return string(c) 306 } 307 if c == DefaultColor { 308 return "-" 309 } 310 col := c.Color().TrueColor().Hex() 311 if col < 0 { 312 return "-" 313 } 314 315 return fmt.Sprintf("#%06x", col) 316 } 317 318 func (c Color) isHex() bool { 319 return len(c) == 7 && c[0] == '#' 320 } 321 322 // Color returns a view color. 323 func (c Color) Color() tcell.Color { 324 if c == DefaultColor { 325 return tcell.ColorDefault 326 } 327 328 return tcell.GetColor(string(c)).TrueColor() 329 }