github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/webapp/components/product-builder.js (about)

     1  /**
     2   * Copyright 2018 The WPT Dashboard Project. All rights reserved.
     3   * Use of this source code is governed by a BSD-style license that can be
     4   * found in the LICENSE file.
     5   */
     6  
     7  import '../node_modules/@polymer/paper-card/paper-card.js';
     8  import '../node_modules/@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
     9  import '../node_modules/@polymer/paper-icon-button/paper-icon-button.js';
    10  import '../node_modules/@polymer/paper-input/paper-input.js';
    11  import '../node_modules/@polymer/paper-item/paper-icon-item.js';
    12  import '../node_modules/@polymer/paper-item/paper-item.js';
    13  import '../node_modules/@polymer/paper-listbox/paper-listbox.js';
    14  import '../node_modules/@polymer/polymer/lib/elements/dom-if.js';
    15  import '../node_modules/@polymer/polymer/lib/elements/dom-repeat.js';
    16  import { html } from '../node_modules/@polymer/polymer/polymer-element.js';
    17  import { PolymerElement } from '../node_modules/@polymer/polymer/polymer-element.js';
    18  import './display-logo.js';
    19  import './browser-picker.js';
    20  import { Channels, DefaultBrowserNames, ProductInfo, SemanticLabels, Sources } from './product-info.js';
    21  
    22  class ProductBuilder extends ProductInfo(PolymerElement) {
    23    static get template() {
    24      return html`
    25      <style>
    26        paper-icon-button {
    27          float: right;
    28        }
    29        display-logo[small] {
    30          margin-top: 16px;
    31        }
    32        .source {
    33          height: 24px;
    34          width: 24px;
    35        }
    36      </style>
    37      <paper-card>
    38        <div class="card-content">
    39          <paper-icon-button icon="delete" onclick="[[deleteProduct]]"></paper-icon-button>
    40  
    41          <display-logo product="[[_product]]"></display-logo>
    42          <template is="dom-if" if="[[debug]]">
    43            [[spec]]
    44          </template>
    45  
    46          <br>
    47          <browser-picker browser="{{browserName}}" products="[[allProducts]]"></browser-picker>
    48  
    49          <br>
    50          <paper-dropdown-menu label="Channel" no-animations>
    51            <paper-listbox slot="dropdown-content" selected="{{ _channel }}" attr-for-selected="value">
    52              <paper-item value="any">Any</paper-item>
    53              <template is="dom-repeat" items="[[channels]]" as="channel">
    54                <paper-icon-item value="[[channel]]">
    55                  <display-logo slot="item-icon" product="[[productWithChannel(_product, channel)]]" small></display-logo>
    56                  [[displayName(channel)]]
    57                </paper-icon-item>
    58              </template>
    59            </paper-listbox>
    60          </paper-dropdown-menu>
    61  
    62          <br>
    63          <paper-dropdown-menu label="Source" no-animations>
    64            <paper-listbox slot="dropdown-content" selected="{{ _source }}" attr-for-selected="value">
    65              <paper-item value="any">Any</paper-item>
    66              <template is="dom-repeat" items="[[sources]]" as="source">
    67                <paper-icon-item value="[[source]]">
    68                  <img slot="item-icon" class="source" src="/static/[[source]].svg">
    69                  [[displayName(source)]]
    70                </paper-icon-item>
    71              </template>
    72            </paper-listbox>
    73          </paper-dropdown-menu>
    74  
    75          <br>
    76          <paper-input-container always-float-label>
    77            <label slot="label">Version</label>
    78            <input slot="input" placeholder="(Any version)" list="versions-datalist" value="{{ browserVersion::input }}">
    79            <datalist id="versions-datalist"></datalist>
    80          </paper-input-container>
    81        </div></paper-card>
    82  `;
    83    }
    84  
    85    static get is() {
    86      return 'product-builder';
    87    }
    88  
    89    static get properties() {
    90      return {
    91        browserName: {
    92          type: String,
    93          value: DefaultBrowserNames[0],
    94          notify: true,
    95        },
    96        browserVersion: {
    97          type: String,
    98          value: '',
    99          notify: true,
   100        },
   101        labels: {
   102          type: Array,
   103          value: [],
   104          notify: true,
   105          observer: 'labelsChanged',
   106        },
   107        /*
   108          _product is a local re-aggregation of the fields, used for
   109          display-logo, and notifying parents of changes.
   110        */
   111        _product: {
   112          type: Object,
   113          computed: 'computeProduct(browserName, browserVersion, labels)',
   114          notify: true,
   115        },
   116        _channel: {
   117          type: String,
   118          value: 'any',
   119          observer: 'semanticLabelChanged',
   120        },
   121        _source: {
   122          type: String,
   123          value: 'any',
   124          observer: 'semanticLabelChanged',
   125        },
   126        spec: {
   127          type: String,
   128          computed: 'computeSpec(_product)',
   129        },
   130        debug: {
   131          type: Boolean,
   132          value: false,
   133        },
   134        onDelete: Function,
   135        onProductChanged: Function,
   136        channels: {
   137          type: Array,
   138          value: Array.from(Channels),
   139        },
   140        sources: {
   141          type: Array,
   142          value: Array.from(Sources),
   143        },
   144        versionsURL: {
   145          type: String,
   146          computed: 'computeVersionsURL(_product)',
   147          observer: 'versionsURLUpdated',
   148        },
   149        versions: {
   150          type: Array,
   151        },
   152        versionsAutocomplete: {
   153          type: Array,
   154          observer: 'versionsAutocompleteUpdated'
   155        },
   156      };
   157    }
   158  
   159    constructor() {
   160      super();
   161      this.deleteProduct = () => {
   162        this.onDelete && this.onDelete(this.product);
   163      };
   164      this._createMethodObserver('versionsUpdated(browserVersion, versions)');
   165    }
   166  
   167    computeProduct(browserName, browserVersion, labels) {
   168      const product = {
   169        browser_name: browserName,
   170        browser_version: browserVersion,
   171        labels: labels,
   172      };
   173      this.onProductChanged && this.onProductChanged(product);
   174      return product;
   175    }
   176  
   177    computeSpec(product) {
   178      return this.getSpec(product);
   179    }
   180  
   181    labelsChanged(labels) {
   182      // Configure the channel from the labels.
   183      labels = new Set(labels || []);
   184      for (const semantic of SemanticLabels) {
   185        const value = Array.from(semantic.values).find(c => labels.has(c)) || 'any';
   186        if (this[semantic.property] !== value) {
   187          this[semantic.property] = value;
   188        }
   189      }
   190    }
   191  
   192    semanticLabelChanged(newValue, oldValue) {
   193      // Configure the labels from the semantic label's value.
   194      const isAny = !newValue || newValue === 'any';
   195      let labels = Array.from(this.labels || []);
   196      if (oldValue) {
   197        labels = labels.filter(l => l !== oldValue);
   198      }
   199      if (!isAny && !labels.includes(newValue)) {
   200        labels.push(newValue);
   201      } else if (!oldValue) {
   202        return;
   203      }
   204      this.labels = labels;
   205    }
   206  
   207    productWithChannel(product, channel) {
   208      return Object.assign({}, product, {
   209        labels: (product.labels || []).filter(l => !Channels.has(l)).concat(channel)
   210      });
   211    }
   212  
   213    // Respond to product spec changing by computing a new versions URL.
   214    computeVersionsURL(product) {
   215      product = Object.assign({}, product);
   216      delete product.browser_version;
   217      const url = new URL('/api/versions', window.location);
   218      url.searchParams.set('product', this.getSpec(product));
   219      return url;
   220    }
   221  
   222    // Respond to version URL changing by fetching the versions
   223    versionsURLUpdated(url, urlBefore) {
   224      if (!url || urlBefore === url) {
   225        return;
   226      }
   227      fetch(url).then(r => r.json()).then(v => {
   228        this.versions = v;
   229      });
   230    }
   231  
   232    // Respond to newly fetched versions, or user input, by filtering the autocomplete list.
   233    versionsUpdated(version, versions) {
   234      if (!versions || !versions.length) {
   235        this.versionsAutocomplete = [];
   236        return;
   237      }
   238      if (version) {
   239        versions = versions.filter(s => s.startsWith(version));
   240      }
   241      versions = versions.slice(0, 10);
   242      // Check actually different from current.
   243      const current = new Set(this.versionsAutocomplete || []);
   244      if (current.size === versions.length && !versions.find(v => !current.has(v))) {
   245        return;
   246      }
   247      this.versionsAutocomplete = versions;
   248    }
   249  
   250    versionsAutocompleteUpdated(versionsAutocomplete) {
   251      const datalist = this.shadowRoot.querySelector('datalist');
   252      datalist.innerHTML = '';
   253      for (const sha of versionsAutocomplete) {
   254        const option = document.createElement('option');
   255        option.setAttribute('value', sha);
   256        datalist.appendChild(option);
   257      }
   258    }
   259  }
   260  
   261  window.customElements.define(ProductBuilder.is, ProductBuilder);