UNPKG

@yuebai008/cli

Version:

Command line interface for rapid qg-minigame development

139 lines (130 loc) 20.3 kB
import*as Common from"../../core/common/common.js";import*as i18n from"../../core/i18n/i18n.js";import*as Platform from"../../core/platform/platform.js";import*as Root from"../../core/root/root.js";import*as SDK from"../../core/sdk/sdk.js";import*as TextUtils from"../../models/text_utils/text_utils.js";import*as IconButton from"../../ui/components/icon_button/icon_button.js";import*as DataGrid from"../../ui/legacy/components/data_grid/data_grid.js";import*as Components from"../../ui/legacy/components/utils/utils.js";import*as UI from"../../ui/legacy/legacy.js";import cssOverviewCompletedViewStyles from"./cssOverviewCompletedView.css.js";import{CSSOverviewSidebarPanel}from"./CSSOverviewSidebarPanel.js";const UIStrings={overviewSummary:"Overview summary",colors:"Colors",fontInfo:"Font info",unusedDeclarations:"Unused declarations",mediaQueries:"Media queries",elements:"Elements",externalStylesheets:"External stylesheets",inlineStyleElements:"Inline style elements",styleRules:"Style rules",typeSelectors:"Type selectors",idSelectors:"ID selectors",classSelectors:"Class selectors",universalSelectors:"Universal selectors",attributeSelectors:"Attribute selectors",nonsimpleSelectors:"Non-simple selectors",backgroundColorsS:"Background colors: {PH1}",textColorsS:"Text colors: {PH1}",fillColorsS:"Fill colors: {PH1}",borderColorsS:"Border colors: {PH1}",thereAreNoFonts:"There are no fonts.",thereAreNoUnusedDeclarations:"There are no unused declarations.",thereAreNoMediaQueries:"There are no media queries.",contrastIssues:"Contrast issues",nOccurrences:"{n, plural, =1 {# occurrence} other {# occurrences}}",contrastIssuesS:"Contrast issues: {PH1}",textColorSOverSBackgroundResults:"Text color {PH1} over {PH2} background results in low contrast for {PH3} elements",aa:"AA",aaa:"AAA",apca:"APCA",element:"Element",declaration:"Declaration",source:"Source",contrastRatio:"Contrast ratio",cssOverviewElements:"CSS Overview Elements",showElement:"Show element"},str_=i18n.i18n.registerUIStrings("panels/css_overview/CSSOverviewCompletedView.ts",UIStrings),i18nString=i18n.i18n.getLocalizedString.bind(void 0,str_);function getBorderString(e){let{h:t,s:i,l:s}=e.as("hsl");return t=Math.round(360*t),i=Math.round(100*i),s=Math.round(100*s),s=Math.max(0,s-15),`1px solid hsl(${t}deg ${i}% ${s}%)`}export class CSSOverviewCompletedView extends UI.Panel.PanelWithSidebar{#e;#t;#i;#s;#n;#r;#o;#a;#l;#d;#c;#h;constructor(e){super("css_overview_completed_view"),this.#e=e,this.#t=new Intl.NumberFormat("en-US"),this.#i=new UI.SplitWidget.SplitWidget(!0,!0),this.#s=new UI.Widget.VBox,this.#n=new DetailsView,this.#n.addEventListener("TabClosed",(e=>{0===e.data&&this.#i.setSidebarMinimized(!0)})),this.#i.setMainWidget(this.#s),this.#i.setSidebarWidget(this.#n),this.#i.setVertical(!1),this.#i.setSecondIsSidebar(!0),this.#i.setSidebarMinimized(!0),this.#r=new CSSOverviewSidebarPanel,this.#r.setMinimumSize(100,25),this.splitWidget().setSidebarWidget(this.#r),this.splitWidget().setMainWidget(this.#i),this.#l=new Components.Linkifier.Linkifier(20,!0),this.#d=new Map,this.#r.addItem(i18nString(UIStrings.overviewSummary),"summary"),this.#r.addItem(i18nString(UIStrings.colors),"colors"),this.#r.addItem(i18nString(UIStrings.fontInfo),"font-info"),this.#r.addItem(i18nString(UIStrings.unusedDeclarations),"unused-declarations"),this.#r.addItem(i18nString(UIStrings.mediaQueries),"media-queries"),this.#r.select("summary",!1),this.#r.addEventListener("ItemSelected",this.#m,this),this.#r.addEventListener("Reset",this.#u,this),this.#e.addEventListener("Reset",this.#g,this),this.#e.addEventListener("PopulateNodes",this.#v,this),this.#s.element.addEventListener("click",this.#S.bind(this)),this.#c=null}wasShown(){super.wasShown(),this.#i.registerCSSFiles([cssOverviewCompletedViewStyles]),this.registerCSSFiles([cssOverviewCompletedViewStyles])}initializeModels(e){const t=e.model(SDK.CSSModel.CSSModel),i=e.model(SDK.DOMModel.DOMModel);if(!t||!i)throw new Error("Target must provide CSS and DOM models");this.#o=t,this.#a=i}#m(e){const{data:t}=e,i=this.#h.$(t.id);if(i&&(i.scrollIntoView(),!t.isMouseEvent&&"Enter"===t.key)){const e=i.querySelector('button, [tabindex="0"]');e?.focus()}}#u(){this.#e.dispatchEventToListeners("Reset")}#g(){this.#s.element.removeChildren(),this.#i.setSidebarMinimized(!0),this.#n.closeTabs(),this.#d=new Map,CSSOverviewCompletedView.pushedNodes.clear(),this.#r.select("summary",!1)}#S(e){if(!e.target)return;const t=e.target.dataset,i=t.type;if(!i||!this.#c)return;let s;switch(i){case"contrast":{const e=t.section,n=t.key;if(!n)return;s={type:i,key:n,nodes:this.#c.textColorContrastIssues.get(n)||[],section:e};break}case"color":{const e=t.color,n=t.section;if(!e)return;let r;switch(n){case"text":r=this.#c.textColors.get(e);break;case"background":r=this.#c.backgroundColors.get(e);break;case"fill":r=this.#c.fillColors.get(e);break;case"border":r=this.#c.borderColors.get(e)}if(!r)return;r=Array.from(r).map((e=>({nodeId:e}))),s={type:i,color:e,nodes:r,section:n};break}case"unused-declarations":{const e=t.declaration;if(!e)return;const n=this.#c.unusedDeclarations.get(e);if(!n)return;s={type:i,declaration:e,nodes:n};break}case"media-queries":{const e=t.text;if(!e)return;const n=this.#c.mediaQueries.get(e);if(!n)return;s={type:i,text:e,nodes:n};break}case"font-info":{const e=t.value;if(!t.path)return;const[n,r]=t.path.split("/");if(!e)return;const o=this.#c.fontInfo.get(n);if(!o)return;const a=o.get(r);if(!a)return;const l=a.get(e);if(!l)return;s={type:i,name:`${e} (${n}, ${r})`,nodes:l.map((e=>({nodeId:e})))};break}default:return}e.consume(),this.#e.dispatchEventToListeners("PopulateNodes",{payload:s}),this.#i.setSidebarMinimized(!1)}async#p(e){if(!e||!("backgroundColors"in e)||!("textColors"in e))return;this.#c=e;const{elementCount:t,backgroundColors:i,textColors:s,textColorContrastIssues:n,fillColors:r,borderColors:o,globalStyleStats:a,mediaQueries:l,unusedDeclarations:d,fontInfo:c}=this.#c,h=this.#b(i),m=this.#b(s),u=this.#b(r),g=this.#b(o);this.#h=UI.Fragment.Fragment.build` <div class="vbox overview-completed-view"> <div $="summary" class="results-section horizontally-padded summary"> <h1>${i18nString(UIStrings.overviewSummary)}</h1> <ul> <li> <div class="label">${i18nString(UIStrings.elements)}</div> <div class="value">${this.#t.format(t)}</div> </li> <li> <div class="label">${i18nString(UIStrings.externalStylesheets)}</div> <div class="value">${this.#t.format(a.externalSheets)}</div> </li> <li> <div class="label">${i18nString(UIStrings.inlineStyleElements)}</div> <div class="value">${this.#t.format(a.inlineStyles)}</div> </li> <li> <div class="label">${i18nString(UIStrings.styleRules)}</div> <div class="value">${this.#t.format(a.styleRules)}</div> </li> <li> <div class="label">${i18nString(UIStrings.mediaQueries)}</div> <div class="value">${this.#t.format(l.size)}</div> </li> <li> <div class="label">${i18nString(UIStrings.typeSelectors)}</div> <div class="value">${this.#t.format(a.stats.type)}</div> </li> <li> <div class="label">${i18nString(UIStrings.idSelectors)}</div> <div class="value">${this.#t.format(a.stats.id)}</div> </li> <li> <div class="label">${i18nString(UIStrings.classSelectors)}</div> <div class="value">${this.#t.format(a.stats.class)}</div> </li> <li> <div class="label">${i18nString(UIStrings.universalSelectors)}</div> <div class="value">${this.#t.format(a.stats.universal)}</div> </li> <li> <div class="label">${i18nString(UIStrings.attributeSelectors)}</div> <div class="value">${this.#t.format(a.stats.attribute)}</div> </li> <li> <div class="label">${i18nString(UIStrings.nonsimpleSelectors)}</div> <div class="value">${this.#t.format(a.stats.nonSimple)}</div> </li> </ul> </div> <div $="colors" class="results-section horizontally-padded colors"> <h1>${i18nString(UIStrings.colors)}</h1> <h2>${i18nString(UIStrings.backgroundColorsS,{PH1:h.length})}</h2> <ul> ${h.map(this.#C.bind(this,"background"))} </ul> <h2>${i18nString(UIStrings.textColorsS,{PH1:m.length})}</h2> <ul> ${m.map(this.#C.bind(this,"text"))} </ul> ${n.size>0?this.#f(n):""} <h2>${i18nString(UIStrings.fillColorsS,{PH1:u.length})}</h2> <ul> ${u.map(this.#C.bind(this,"fill"))} </ul> <h2>${i18nString(UIStrings.borderColorsS,{PH1:g.length})}</h2> <ul> ${g.map(this.#C.bind(this,"border"))} </ul> </div> <div $="font-info" class="results-section font-info"> <h1>${i18nString(UIStrings.fontInfo)}</h1> ${c.size>0?this.#I(c):UI.Fragment.Fragment.build`<div>${i18nString(UIStrings.thereAreNoFonts)}</div>`} </div> <div $="unused-declarations" class="results-section unused-declarations"> <h1>${i18nString(UIStrings.unusedDeclarations)}</h1> ${d.size>0?this.#$(d,"unused-declarations","declaration"):UI.Fragment.Fragment.build`<div class="horizontally-padded">${i18nString(UIStrings.thereAreNoUnusedDeclarations)}</div>`} </div> <div $="media-queries" class="results-section media-queries"> <h1>${i18nString(UIStrings.mediaQueries)}</h1> ${l.size>0?this.#$(l,"media-queries","text"):UI.Fragment.Fragment.build`<div class="horizontally-padded">${i18nString(UIStrings.thereAreNoMediaQueries)}</div>`} </div> </div>`,this.#s.element.appendChild(this.#h.element())}#v(e){const{payload:t}=e.data;let i="",s="";switch(t.type){case"contrast":{const{section:e,key:n}=t;i=`${e}-${n}`,s=i18nString(UIStrings.contrastIssues);break}case"color":{const{section:e,color:n}=t;i=`${e}-${n}`,s=`${n.toUpperCase()} (${e})`;break}case"unused-declarations":{const{declaration:e}=t;i=`${e}`,s=`${e}`;break}case"media-queries":{const{text:e}=t;i=`${e}`,s=`${e}`;break}case"font-info":{const{name:e}=t;i=`${e}`,s=`${e}`;break}}let n=this.#d.get(i);if(!n){if(!this.#a||!this.#o)throw new Error("Unable to initialize CSS Overview, missing models");n=new ElementDetailsView(this.#e,this.#a,this.#o,this.#l),n.populateNodes(t.nodes),this.#d.set(i,n)}this.#n.appendTab(i,s,n,!0)}#I(e){const t=Array.from(e.entries());return UI.Fragment.Fragment.build` ${t.map((([e,t])=>UI.Fragment.Fragment.build`<section class="font-family"><h2>${e}</h2> ${this.#w(e,t)}</section>`))} `}#w(e,t){const i=Array.from(t.entries());return UI.Fragment.Fragment.build` <div class="font-metric"> ${i.map((([t,i])=>{const s=`${e}/${t}`;return UI.Fragment.Fragment.build` <div> <h3>${t}</h3> ${this.#$(i,"font-info","value",s)} </div>`}))} </div>`}#$(e,t,i,s=""){const n=Array.from(e.entries()).sort(((e,t)=>{const i=e[1];return t[1].length-i.length})),r=n.reduce(((e,t)=>e+t[1].length),0);return UI.Fragment.Fragment.build`<ul> ${n.map((([e,n])=>{const o=100*n.length/r,a=i18nString(UIStrings.nOccurrences,{n:n.length});return UI.Fragment.Fragment.build`<li> <div class="title">${e}</div> <button data-type="${t}" data-path="${s}" data-${i}="${e}"> <div class="details">${a}</div> <div class="bar-container"> <div class="bar" style="width: ${o}%;"></div> </div> </button> </li>`}))} </ul>`}#f(e){return UI.Fragment.Fragment.build` <h2>${i18nString(UIStrings.contrastIssuesS,{PH1:e.size})}</h2> <ul> ${[...e.entries()].map((([e,t])=>this.#y(e,t)))} </ul> `}#y(e,t){console.assert(t.length>0);let i=t[0];for(const e of t)Math.abs(e.contrastRatio)<Math.abs(i.contrastRatio)&&(i=e);const s=i.textColor.asString("hexa"),n=i.backgroundColor.asString("hexa"),r=Root.Runtime.experiments.isEnabled("APCA"),o=i18nString(UIStrings.textColorSOverSBackgroundResults,{PH1:s,PH2:n,PH3:t.length}),a=UI.Fragment.Fragment.build`<li> <button title="${o}" aria-label="${o}" data-type="contrast" data-key="${e}" data-section="contrast" class="block" $="color"> Text </button> <div class="block-title"> <div class="contrast-warning hidden" $="aa"><span class="threshold-label">${i18nString(UIStrings.aa)}</span></div> <div class="contrast-warning hidden" $="aaa"><span class="threshold-label">${i18nString(UIStrings.aaa)}</span></div> <div class="contrast-warning hidden" $="apca"><span class="threshold-label">${i18nString(UIStrings.apca)}</span></div> </div> </li>`;if(r){const e=a.$("apca");i.thresholdsViolated.apca?e.appendChild(createClearIcon()):e.appendChild(createCheckIcon()),e.classList.remove("hidden")}else{const e=a.$("aa");i.thresholdsViolated.aa?e.appendChild(createClearIcon()):e.appendChild(createCheckIcon());const t=a.$("aaa");i.thresholdsViolated.aaa?t.appendChild(createClearIcon()):t.appendChild(createCheckIcon()),e.classList.remove("hidden"),t.classList.remove("hidden")}const l=a.$("color");return l.style.backgroundColor=n,l.style.color=s,l.style.border=getBorderString(i.backgroundColor.asLegacyColor()),a}#C(e,t){const i=UI.Fragment.Fragment.build`<li> <button title=${t} data-type="color" data-color="${t}" data-section="${e}" class="block" $="color"></button> <div class="block-title color-text">${t}</div> </li>`,s=i.$("color");s.style.backgroundColor=t;const n=Common.Color.parse(t)?.asLegacyColor();if(n)return s.style.border=getBorderString(n),i}#b(e){return Array.from(e.keys()).sort(((e,t)=>{const i=Common.Color.parse(e)?.asLegacyColor(),s=Common.Color.parse(t)?.asLegacyColor();return i&&s?Common.ColorUtils.luminance(s.rgba())-Common.ColorUtils.luminance(i.rgba()):0}))}setOverviewData(e){this.#p(e)}static pushedNodes=new Set}export class DetailsView extends(Common.ObjectWrapper.eventMixin(UI.Widget.VBox)){#U;constructor(){super(),this.#U=new UI.TabbedPane.TabbedPane,this.#U.show(this.element),this.#U.addEventListener(UI.TabbedPane.Events.TabClosed,(()=>{this.dispatchEventToListeners("TabClosed",this.#U.tabIds().length)}))}appendTab(e,t,i,s){this.#U.hasTab(e)||this.#U.appendTab(e,t,i,void 0,void 0,s),this.#U.selectTab(e)}closeTabs(){this.#U.closeTabs(this.#U.tabIds())}}export class ElementDetailsView extends UI.Widget.Widget{#e;#a;#o;#l;#k;#x;constructor(e,t,i,s){super(),this.#e=e,this.#a=t,this.#o=i,this.#l=s,this.#k=[{id:"nodeId",title:i18nString(UIStrings.element),sortable:!0,weight:50,titleDOMFragment:void 0,sort:void 0,align:void 0,width:void 0,fixedWidth:void 0,editable:void 0,nonSelectable:void 0,longText:void 0,disclosure:void 0,allowInSortByEvenWhenHidden:void 0,dataType:void 0,defaultWeight:void 0},{id:"declaration",title:i18nString(UIStrings.declaration),sortable:!0,weight:50,titleDOMFragment:void 0,sort:void 0,align:void 0,width:void 0,fixedWidth:void 0,editable:void 0,nonSelectable:void 0,longText:void 0,disclosure:void 0,allowInSortByEvenWhenHidden:void 0,dataType:void 0,defaultWeight:void 0},{id:"sourceURL",title:i18nString(UIStrings.source),sortable:!1,weight:100,titleDOMFragment:void 0,sort:void 0,align:void 0,width:void 0,fixedWidth:void 0,editable:void 0,nonSelectable:void 0,longText:void 0,disclosure:void 0,allowInSortByEvenWhenHidden:void 0,dataType:void 0,defaultWeight:void 0},{id:"contrastRatio",title:i18nString(UIStrings.contrastRatio),sortable:!0,weight:25,titleDOMFragment:void 0,sort:void 0,align:void 0,width:"150px",fixedWidth:!0,editable:void 0,nonSelectable:void 0,longText:void 0,disclosure:void 0,allowInSortByEvenWhenHidden:void 0,dataType:void 0,defaultWeight:void 0}],this.#x=new DataGrid.SortableDataGrid.SortableDataGrid({displayName:i18nString(UIStrings.cssOverviewElements),columns:this.#k,editCallback:void 0,deleteCallback:void 0,refreshCallback:void 0}),this.#x.element.classList.add("element-grid"),this.#x.element.addEventListener("mouseover",this.#F.bind(this)),this.#x.setStriped(!0),this.#x.addEventListener(DataGrid.DataGrid.Events.SortingChanged,this.#M.bind(this)),this.#x.asWidget().show(this.element)}#M(){const e=this.#x.sortColumnId();if(!e)return;const t=DataGrid.SortableDataGrid.SortableDataGrid.StringComparator.bind(null,e);this.#x.sortNodes(t,!this.#x.isSortOrderAscending())}#F(e){const t=e.composedPath().find((e=>e.dataset&&e.dataset.backendNodeId));if(!t)return;const i=Number(t.dataset.backendNodeId);this.#e.dispatchEventToListeners("RequestNodeHighlight",i)}async populateNodes(e){if(this.#x.rootNode().removeChildren(),!e.length)return;const[t]=e,i=new Set;let s;if("nodeId"in t&&t.nodeId&&i.add("nodeId"),"declaration"in t&&t.declaration&&i.add("declaration"),"sourceURL"in t&&t.sourceURL&&i.add("sourceURL"),"contrastRatio"in t&&t.contrastRatio&&i.add("contrastRatio"),"nodeId"in t&&i.has("nodeId")){const t=e.reduce(((e,t)=>{const i=t.nodeId;return CSSOverviewCompletedView.pushedNodes.has(i)?e:(CSSOverviewCompletedView.pushedNodes.add(i),e.add(i))}),new Set);s=await this.#a.pushNodesByBackendIdsToFrontend(t)}for(const t of e){let e;if("nodeId"in t&&i.has("nodeId")){if(!s)continue;if(e=s.get(t.nodeId),!e)continue}const n=new ElementNode(t,e,this.#l,this.#o);n.selectable=!1,this.#x.insertChild(n)}this.#x.setColumnsVisiblity(i),this.#x.renderInline(),this.#x.wasShown()}}export class ElementNode extends DataGrid.SortableDataGrid.SortableDataGridNode{#l;#o;#T;constructor(e,t,i,s){super(e),this.#T=t,this.#l=i,this.#o=s}createCell(e){const t=this.#T;if("nodeId"===e){const i=this.createTD(e);if(i.textContent="...",!t)throw new Error("Node entry is missing a related frontend node.");return Common.Linkifier.Linkifier.linkify(t).then((e=>{i.textContent="",e.dataset.backendNodeId=t.backendNodeId().toString(),i.appendChild(e);const s=new IconButton.Icon.Icon;s.data={iconName:"select-element",color:"var(--icon-show-element)",width:"16px"},s.classList.add("show-element"),UI.Tooltip.Tooltip.install(s,i18nString(UIStrings.showElement)),s.tabIndex=0,s.onclick=()=>t.scrollIntoView(),i.appendChild(s)})),i}if("sourceURL"===e){const t=this.createTD(e);if(this.data.range){const e=this.#L(this.#o,this.#l,this.data.styleSheetId,TextUtils.TextRange.TextRange.fromObject(this.data.range));e&&""!==e.textContent?t.appendChild(e):t.textContent="(unable to link)"}else t.textContent="(unable to link to inlined styles)";return t}if("contrastRatio"===e){const t=this.createTD(e),i=Root.Runtime.experiments.isEnabled("APCA"),s=Platform.NumberUtilities.floor(this.data.contrastRatio,2),n=i?s+"%":s,r=getBorderString(this.data.backgroundColor),o=this.data.textColor.asString(),a=this.data.backgroundColor.asString(),l=UI.Fragment.Fragment.build` <div class="contrast-container-in-grid" $="container"> <span class="contrast-preview" style="border: ${r}; color: ${o}; background-color: ${a};">Aa</span> <span>${n}</span> </div> `,d=l.$("container");return i?(d.append(UI.Fragment.Fragment.build`<span>${i18nString(UIStrings.apca)}</span>`.element()),this.data.thresholdsViolated.apca?d.appendChild(createClearIcon()):d.appendChild(createCheckIcon())):(d.append(UI.Fragment.Fragment.build`<span>${i18nString(UIStrings.aa)}</span>`.element()),this.data.thresholdsViolated.aa?d.appendChild(createClearIcon()):d.appendChild(createCheckIcon()),d.append(UI.Fragment.Fragment.build`<span>${i18nString(UIStrings.aaa)}</span>`.element()),this.data.thresholdsViolated.aaa?d.appendChild(createClearIcon()):d.appendChild(createCheckIcon())),t.appendChild(l.element()),t}return super.createCell(e)}#L(e,t,i,s){const n=e.styleSheetHeaderForId(i);if(!n)return;const r=n.lineNumberInSource(s.startLine),o=n.columnNumberInSource(s.startLine,s.startColumn),a=new SDK.CSSModel.CSSLocation(n,r,o);return t.linkifyCSSLocation(a)}}function createClearIcon(){const e=new IconButton.Icon.Icon;return e.data={iconName:"clear",color:"var(--icon-error)",width:"14px",height:"14px"},e}function createCheckIcon(){const e=new IconButton.Icon.Icon;return e.data={iconName:"checkmark",color:"var(--icon-checkmark-green)",width:"14px",height:"14px"},e}