-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SuperSize] Viewer: Reactor UI state code.
SuperSize Viewer UI states are assigned using query params or UI elements, and has complex interaction, e.g.: * Each state has a default value. These are specified in the HTML and are represented by absence in query params. * Some elements (e.g., Method Count Mode) can disable others (e.g., Symbol Types to Show). * Values need to be serialized when passed to tree-worker-wasm.js. * Validation is needed. * Some states (e.g., Symbol Types to Show) are represented as a short string in query param. Some issues: * |state| provides centralized access of UI states, but sometimes direct access to query params or controls are spotted. * Query param is passed as serialized UI states to tree-worker-wasm.js, requiring redundant parsing and default assignment. * |state|'s usage of URLSearchParam as main storage is awkward, and leads to some churn (e.g., needing to merge Symbol Types to Show &type= values). * There are scattered and repeated code to access UI elements. This CL refactors the above. Key changes: * Use smart UI variables to manage: * Value read / write. * Default values (read from UI elements). * Read query param to assign initial value and update UI elements. * Write to query param. * Sync from UI elements on change (triggered centrally by #frm-options change event). * Centralize UI element access. * Pass exported UI states as object to tree-worker-wasm.js. * Clean up HTML to improve consistency; fix validation errors. New file: * dom.js: For |dom| utility and MainElement with |g_el|. New classes: * UiState -> QueryParamUiState -> ElementUiState for smart UI variables. * MainState for singleton |state| as central storage of UI variables, along with "diff mode" get / set. * MainElement for singleton |g_el| to centralize DOM access. Widescale changes: * Rename element ids to include type prefix, with matching CSS changes. * Centralize document.{querySelector{All},getElementById}() to dom.js. * Direct access to DOM elements goes through |g_el| variables. * Accessing sub-elements via |elt.querySelector*()| is allowed. Additional fixes: * ArtifactInfocard._updateInfocard(): Array out of bound if no diff. * _makeIconTemplateGetter: |symbolIcons|: Noted that .generatedicon does not exist, so just stub with null (for key "*"). * Disassembly "Close" button missing style from redundant class. Bug: 881319, 1186921 Change-Id: Icf0ec13bacbd2546fea5ca82f61ccb29ab631a6d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4232264 Reviewed-by: Andrew Grieve <agrieve@chromium.org> Commit-Queue: Samuel Huang <huangs@chromium.org> Cr-Commit-Position: refs/heads/main@{#1103864}
- Loading branch information
1 parent
99008c5
commit cbb9fd0
Showing
12 changed files
with
949 additions
and
611 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
'use strict'; | ||
|
||
/** Utilities for working with the DOM */ | ||
const dom = { | ||
/** | ||
* Creates a document fragment from the given nodes. | ||
* @param {Iterable<Node>} nodes | ||
* @return {DocumentFragment} | ||
*/ | ||
createFragment(nodes) { | ||
const fragment = document.createDocumentFragment(); | ||
for (const node of nodes) | ||
fragment.appendChild(node); | ||
return fragment; | ||
}, | ||
/** | ||
* Removes all the existing children of `parent` and inserts `newChild` in | ||
* their place. | ||
* @param {!Element} parent | ||
* @param {Node | null} newChild | ||
*/ | ||
replace(parent, newChild) { | ||
parent.innerHTML = ''; | ||
if (newChild) | ||
parent.appendChild(newChild); | ||
}, | ||
/** | ||
* Builds a text element in a single statement. | ||
* @param {string} tagName Type of the element, such as "span". | ||
* @param {string} text Text content for the element. | ||
* @param {string} [className] Class to apply to the element. | ||
*/ | ||
textElement(tagName, text, className) { | ||
const element = document.createElement(tagName); | ||
element.textContent = text; | ||
if (className) | ||
element.className = className; | ||
return element; | ||
}, | ||
}; | ||
|
||
/** Centralized object for element access. */ | ||
class MainElements { | ||
constructor() { | ||
/** @public {!NodeList} Elements that toggle body.show-options on click. */ | ||
this.nlShowOptions = | ||
/** @type {!NodeList} */ (document.querySelectorAll('.toggle-options')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divReviewInfo = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-review-info')); | ||
|
||
/** @type {!HTMLAnchorElement} */ | ||
this.linkReviewText = | ||
/** @type {!HTMLAnchorElement} */ (this.query('#link-review-text')); | ||
|
||
/** @type {!HTMLAnchorElement} */ | ||
this.linkDownloadBefore = | ||
/** @type {!HTMLAnchorElement} */ (this.query('#link-download-before')); | ||
|
||
/** @type {!HTMLAnchorElement} */ | ||
this.linkDownloadLoad = | ||
/** @type {!HTMLAnchorElement} */ (this.query('#link-download-load')); | ||
|
||
/** @type {!HTMLInputElement} */ | ||
this.fileUpload = | ||
/** @type {!HTMLInputElement} */ (this.query('#file-upload')); | ||
|
||
/** @type {!HTMLAnchorElement} */ | ||
this.linkFaq = | ||
/** @type {!HTMLAnchorElement} */ (this.query('#link-faq')); | ||
|
||
/** @type {!HTMLProgressElement} */ | ||
this.progAppbar = | ||
/** @type {!HTMLProgressElement} */ (this.query('#prog-appbar')); | ||
|
||
/** @public {!HTMLFormElement} Form with options and filters. */ | ||
this.frmOptions = | ||
/** @type {!HTMLFormElement} */ (this.query('#frm-options')); | ||
|
||
/** @public {!HTMLInputElement} */ | ||
this.cbMethodCount = | ||
/** @type {!HTMLInputElement} */ (this.query('#cb-method-count')); | ||
|
||
/** @public {!HTMLSelectElement} */ | ||
this.selByteUnit = | ||
/** @type {!HTMLSelectElement} */ (this.query('#sel-byte-unit')); | ||
|
||
/** @public {!HTMLInputElement} */ | ||
this.nbMinSize = | ||
/** @type {!HTMLInputElement} */ (this.query('#nb-min-size')); | ||
|
||
/** @public {!RadioNodeList} */ | ||
this.rnlGroupBy = /** @type {!RadioNodeList} */ ( | ||
this.frmOptions.elements.namedItem(STATE_KEY.GROUP_BY)); | ||
assert(this.rnlGroupBy.length > 0); | ||
|
||
/** @public {!HTMLInputElement} */ | ||
this.tbIncludeRegex = | ||
/** @type {!HTMLInputElement} */ (this.query('#tb-include-regex')); | ||
|
||
/** @public {!HTMLInputElement} */ | ||
this.tbExcludeRegex = | ||
/** @type {!HTMLInputElement} */ (this.query('#tb-exclude-regex')); | ||
|
||
/** @public {!RadioNodeList} */ | ||
this.rnlType = /** @type {!RadioNodeList} */ ( | ||
this.frmOptions.elements.namedItem(STATE_KEY.TYPE)); | ||
assert(this.rnlType.length > 0); | ||
|
||
/** @type {!HTMLFieldSetElement} */ | ||
this.fsTypesFilter = | ||
/** @type {!HTMLFieldSetElement} */ (this.query('#fs-types-filter')); | ||
|
||
/** @type {!HTMLButtonElement} */ | ||
this.btnTypeAll = | ||
/** @type {!HTMLButtonElement} */ (this.query('#btn-type-all')); | ||
|
||
/** @type {!HTMLButtonElement} */ | ||
this.btnTypeNone = | ||
/** @type {!HTMLButtonElement} */ (this.query('#btn-type-none')); | ||
|
||
/** @public {!RadioNodeList} */ | ||
this.rnlFlagFilter = /** @type {!RadioNodeList} */ ( | ||
this.frmOptions.elements.namedItem(STATE_KEY.FLAG_FILTER)); | ||
assert(this.rnlFlagFilter.length > 0); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divIcons = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-icons')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divDiffStatusIcons = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-diff-status-icons')); | ||
|
||
/** @type {!HTMLTemplateElement} Template for groups in the tree. */ | ||
this.tmplSymbolTreeGroup = /** @type {!HTMLTemplateElement} */ ( | ||
this.query('#tmpl-symbol-tree-group')); | ||
|
||
/** @type {!HTMLTemplateElement} Template for leaves in the tree. */ | ||
this.tmplSymbolTreeLeaf = /** @type {!HTMLTemplateElement} */ ( | ||
this.query('#tmpl-symbol-tree-leaf')); | ||
|
||
/** @type {!HTMLSpanElement} */ | ||
this.spanSizeHeader = | ||
/** @type {!HTMLSpanElement} */ (this.query('#span-size-header')); | ||
|
||
/** @type {!HTMLUListElement} */ | ||
this.ulSymbolTree = | ||
/** @type {!HTMLUListElement} */ (this.query('#ul-symbol-tree')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divNoSymbolsMsg = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-no-symbols-msg')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divMetadataView = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-metadata-view')); | ||
|
||
/** @public {!HTMLPreElement} */ | ||
this.preMetadataContent = | ||
/** @type {!HTMLPreElement} */ (this.query('#pre-metadata-content')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divInfocardArtifact = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-infocard-artifact')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divInfocardSymbol = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-infocard-symbol')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divSigninModal = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-signin-modal')); | ||
|
||
/** @public {!HTMLDivElement} */ | ||
this.divDisassemblyModal = | ||
/** @type {!HTMLDivElement} */ (this.query('#div-disassembly-modal')); | ||
} | ||
|
||
/** | ||
* @param {string} q Query string. | ||
* @return {!Element} | ||
* @private | ||
*/ | ||
query(q) { | ||
return /** @type {!Element} */ (assertNotNull(document.querySelector(q))); | ||
} | ||
|
||
/** | ||
* @param {!Element} elt | ||
* @return {!Element} | ||
* @public | ||
*/ | ||
getAriaDescribedBy(elt) { | ||
const id = assertNotNull(elt.getAttribute('aria-describedby')); | ||
return assertNotNull(document.getElementById(id)); | ||
} | ||
} | ||
|
||
/** @const {!MainElements} */ | ||
const g_el = new MainElements(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.