1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { BaseElement, element } from '../../base-ui/BaseElement'; 17 18@element('sp-flags') 19export class SpFlags extends BaseElement { 20 private bodyEl: HTMLElement | undefined | null; 21 22 initElements(): void { 23 let parentElement = this.parentNode as HTMLElement; 24 parentElement.style.overflow = 'hidden'; 25 this.bodyEl = this.shadowRoot?.querySelector('.body'); 26 this.initConfigList(); 27 } 28 29 initHtml(): string { 30 return ` 31 ${this.initHtmlStyle()} 32 <div class="sp-flags-vessel"> 33 <div class="body"> 34 <h3 class="title">Feature flags</h3> 35 </div> 36 </div> 37 `; 38 } 39 40 private initHtmlStyle(): string { 41 return ` 42 <style> 43 .sp-flags-vessel { 44 background-color: var(--dark-background5,#F6F6F6); 45 min-height: 100%; 46 display: grid; 47 grid-template-columns: 1fr; 48 grid-template-rows:1fr; 49 } 50 :host{ 51 width: 100%; 52 height: 100%; 53 background-color: var(--dark-background5,#F6F6F6); 54 display: block; 55 } 56 .body{ 57 width: 85%; 58 margin: 2% 5% 2% 5%; 59 background-color: var(--dark-background3,#FFFFFF); 60 border-radius: 16px 16px 16px 16px; 61 padding-left: 2%; 62 padding-right: 4%; 63 } 64 .title { 65 padding-left: 2%; 66 margin-left: 8%; 67 } 68 .flag-widget { 69 width: 80%; 70 padding: 1% 2% 1% 2%; 71 margin-left: 8%; 72 margin-right: 8%; 73 border-radius: 10px 10px 10px 10px; 74 } 75 .flag-widget:nth-child(2n+1) { 76 background-color: #F5F5F5; 77 } 78 .flag-title-label { 79 margin-right: 10px; 80 flex-grow: 1; 81 text-align: left; 82 opacity: 0.9; 83 font-family: Helvetica-Bold; 84 font-size: 16px; 85 color: #000000; 86 line-height: 28px; 87 font-weight: 700; 88 } 89 .flag-head-div { 90 display: flex; 91 align-items: center; 92 } 93 .flag-des-div { 94 opacity: 0.6; 95 font-family: Helvetica; 96 font-size: 12px; 97 color: var(--dark-color,#000000); 98 text-align: left; 99 line-height: 20px; 100 font-weight: 400; 101 margin-top: 0.1%; 102 } 103 .config_footer { 104 margin-top: 1%; 105 } 106 .flag-select { 107 width: 12rem; 108 border: 1px solid var(--dark-color1,#4D4D4D); 109 border-radius: 16px; 110 opacity: 0.6; 111 font-family: Helvetica; 112 font-size: 12px; 113 color: var(--dark-color1,#000000); 114 text-align: center; 115 line-height: 20px; 116 font-weight: 400; 117 -webkit-appearance: none; 118 background: url(img/down.png) no-repeat 96% center; 119 } 120 .device_label { 121 font-weight: 500; 122 margin-right: 10px; 123 opacity: 0.9; 124 font-family: Helvetica-Bold; 125 font-size: 14px; 126 } 127 .device_input { 128 line-height: 20px; 129 font-weight: 400; 130 margin-right: 2%; 131 border-radius: 16px; 132 border: 1px solid #ccc; 133 padding-left: 10px; 134 } 135 </style> 136 `; 137 } 138 139 private createConfigDiv(): HTMLDivElement { 140 let configDiv = document.createElement('div'); 141 configDiv.className = 'flag-widget'; 142 return configDiv; 143 } 144 145 private createCustomDiv(config: FlagConfigItem, configDiv: HTMLDivElement): void { 146 let configHadDiv = document.createElement('div'); 147 configHadDiv.className = 'flag-head-div'; 148 let titleLabel = document.createElement('label'); 149 titleLabel.textContent = config.title; 150 titleLabel.className = 'flag-title-label'; 151 let configSelect = document.createElement('select'); 152 configSelect.className = 'flag-select'; 153 configSelect.setAttribute('title', config.title); 154 config.switchOptions.forEach((optionItem) => { 155 let configOption = document.createElement('option'); 156 configOption.value = optionItem.option; 157 configOption.textContent = optionItem.option; 158 if (optionItem.selected) { 159 configOption.selected = true; 160 } 161 configSelect.appendChild(configOption); 162 }); 163 configSelect.addEventListener('change', () => { 164 let title = configSelect.getAttribute('title'); 165 FlagsConfig.updateFlagsConfig(title!, configSelect.selectedOptions[0].value); 166 }); 167 let description = document.createElement('div'); 168 description.className = 'flag-des-div'; 169 description.textContent = config.describeContent; 170 configHadDiv.appendChild(titleLabel); 171 configHadDiv.appendChild(configSelect); 172 configDiv.appendChild(configHadDiv); 173 configDiv.appendChild(description); 174 } 175 176 private initConfigList(): void { 177 let allConfig = FlagsConfig.getAllFlagConfig(); 178 allConfig.forEach((config) => { 179 let configDiv = this.createConfigDiv(); 180 this.createCustomDiv(config, configDiv); 181 if (config.title === 'AnimationAnalysis') { 182 let configFooterDiv = document.createElement('div'); 183 configFooterDiv.className = 'config_footer'; 184 let deviceWidthLabelEl = document.createElement('label'); 185 deviceWidthLabelEl.className = 'device_label'; 186 deviceWidthLabelEl.textContent = 'PhysicalWidth :'; 187 let deviceWidthEl = document.createElement('input'); 188 deviceWidthEl.value = <string>config.addInfo!.physicalWidth; 189 deviceWidthEl.addEventListener('keyup', () => { 190 deviceWidthEl.value = deviceWidthEl.value.replace(/\D/g, ''); 191 }); 192 deviceWidthEl.addEventListener('blur', () => { 193 if (deviceWidthEl.value !== '') { 194 FlagsConfig.updateFlagsConfig('physicalWidth', Number(deviceWidthEl.value)); 195 } 196 }); 197 deviceWidthEl.className = 'device_input'; 198 let deviceHeightLabelEl = document.createElement('label'); 199 deviceHeightLabelEl.textContent = 'PhysicalHeight :'; 200 deviceHeightLabelEl.className = 'device_label'; 201 let deviceHeightEl = document.createElement('input'); 202 deviceHeightEl.className = 'device_input'; 203 deviceHeightEl.value = <string>config.addInfo!.physicalHeight; 204 deviceHeightEl.addEventListener('keyup', () => { 205 deviceHeightEl.value = deviceHeightEl.value.replace(/\D/g, ''); 206 }); 207 deviceHeightEl.addEventListener('blur', () => { 208 if (deviceWidthEl.value !== '') { 209 FlagsConfig.updateFlagsConfig('physicalHeight', Number(deviceHeightEl.value)); 210 } 211 }); 212 configFooterDiv.appendChild(deviceWidthLabelEl); 213 configFooterDiv.appendChild(deviceWidthEl); 214 configFooterDiv.appendChild(deviceHeightLabelEl); 215 configFooterDiv.appendChild(deviceHeightEl); 216 configDiv.appendChild(configFooterDiv); 217 } 218 this.bodyEl!.appendChild(configDiv); 219 }); 220 } 221} 222 223export type Params = { 224 [key: string]: unknown; 225}; 226 227export class FlagsConfig { 228 static FLAGS_CONFIG_KEY = 'FlagsConfig'; 229 static DEFAULT_CONFIG: Array<FlagConfigItem> = [ 230 { 231 title: 'TaskPool', 232 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 233 describeContent: 'Analyze TaskPool templates', 234 }, 235 { 236 title: 'AnimationAnalysis', 237 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 238 describeContent: 'Analyze Animation effect templates', 239 addInfo: { physicalWidth: 0, physicalHeight: 0 }, 240 }, 241 { 242 title: 'AppStartup', 243 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 244 describeContent: 'App Startup templates', 245 }, 246 { 247 title: 'SchedulingAnalysis', 248 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 249 describeContent: 'Scheduling analysis templates', 250 }, 251 { 252 title: 'BinderRunnable', 253 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 254 describeContent: 'support Cpu State Binder-Runnable', 255 }, 256 { 257 title: 'FfrtConvert', 258 switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], 259 describeContent: 'Ffrt Convert templates', 260 }, 261 ]; 262 263 static getAllFlagConfig(): Array<FlagConfigItem> { 264 let flagsConfigStr = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); 265 if (flagsConfigStr === null) { 266 let flagConfigObj: Params = {}; 267 FlagsConfig.DEFAULT_CONFIG.forEach((config) => { 268 let selectedOption = config.switchOptions.filter((option) => { 269 return option.selected; 270 }); 271 let value = config.switchOptions[0].option; 272 if (selectedOption[0] !== undefined) { 273 value = selectedOption[0].option; 274 } 275 flagConfigObj[config.title] = value; 276 if (config.addInfo) { 277 for (const [key, value] of Object.entries(config.addInfo)) { 278 flagConfigObj[key] = value; 279 } 280 } 281 }); 282 window.localStorage.setItem(FlagsConfig.FLAGS_CONFIG_KEY, JSON.stringify(flagConfigObj)); 283 return FlagsConfig.DEFAULT_CONFIG; 284 } else { 285 let flagsConfig = JSON.parse(flagsConfigStr); 286 FlagsConfig.DEFAULT_CONFIG.forEach((config) => { 287 let cfg = flagsConfig[config.title]; 288 if (cfg) { 289 config.switchOptions.forEach((option) => { 290 if (option.option === cfg) { 291 option.selected = true; 292 } else { 293 option.selected = false; 294 } 295 }); 296 } 297 if (config.addInfo) { 298 for (const [key, value] of Object.entries(config.addInfo)) { 299 let cfg = flagsConfig[key]; 300 if (cfg) { 301 config.addInfo[key] = cfg; 302 } 303 } 304 } 305 }); 306 } 307 return FlagsConfig.DEFAULT_CONFIG; 308 } 309 310 static getSpTraceStreamParseConfig(): string { 311 let parseConfig = {}; 312 FlagsConfig.getAllFlagConfig().forEach((configItem) => { 313 let selectedOption = configItem.switchOptions.filter((option) => { 314 return option.selected; 315 }); 316 // @ts-ignore 317 parseConfig[configItem.title] = selectedOption[0].option === 'Enabled' ? 1 : 0; 318 }); 319 return JSON.stringify({ config: parseConfig }); 320 } 321 322 static getFlagsConfig(flagName: string): Params | undefined { 323 let flagConfigObj: Params = {}; 324 let configItem = FlagsConfig.getAllFlagConfig().find((config) => { 325 return config.title === flagName; 326 }); 327 if (configItem) { 328 let selectedOption = configItem.switchOptions.filter((option) => { 329 return option.selected; 330 }); 331 let value = configItem.switchOptions[0].option; 332 if (selectedOption[0] !== undefined) { 333 value = selectedOption[0].option; 334 } 335 flagConfigObj[configItem.title] = value; 336 if (configItem.addInfo) { 337 for (const [key, value] of Object.entries(configItem.addInfo)) { 338 flagConfigObj[key] = value; 339 } 340 } 341 return flagConfigObj; 342 } else { 343 return configItem; 344 } 345 } 346 347 static getFlagsConfigEnableStatus(flagName: string): boolean { 348 let config = FlagsConfig.getFlagsConfig(flagName); 349 let enable: boolean = false; 350 if (config && config[flagName]) { 351 enable = config[flagName] === 'Enabled'; 352 } 353 return enable; 354 } 355 356 static updateFlagsConfig(key: string, value: unknown): void { 357 let flagsConfigStr = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); 358 let flagConfigObj: Params = {}; 359 if (flagsConfigStr !== null) { 360 flagConfigObj = JSON.parse(flagsConfigStr); 361 } 362 flagConfigObj[key] = value; 363 window.localStorage.setItem(FlagsConfig.FLAGS_CONFIG_KEY, JSON.stringify(flagConfigObj)); 364 } 365} 366 367export interface FlagConfigItem { 368 title: string; 369 switchOptions: OptionItem[]; 370 describeContent: string; 371 addInfo?: Params; 372} 373 374export interface OptionItem { 375 option: string; 376 selected?: boolean; 377} 378