chromium/components/commerce/core/internals/resources/commerce_internals.ts

// 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.

import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
import {getRequiredElement} from 'chrome://resources/js/util.js';

import type {EligibleEntry} from './commerce_internals.mojom-webui.js';
import {CommerceInternalsApiProxy} from './commerce_internals_api_proxy.js';

const SUBSCRIPTION_ROWS =
    ['Cluster ID', 'Domain', 'Price', 'Previous Price', 'Product'];
const PRODUCT_SPECIFICATIONS_ROWS =
    ['ID', 'Creation Time', 'Update Time', 'Name', 'Title', 'URLs'];
const CLUSTER_ID_COLUMN_IDX = 0;
const DOMAIN_COLUMN_IDX = 1;
const CURRENT_PRICE_COLUMN_IDX = 2;
const PREVIOUS_PRICE_COLUMN_IDX = 3;
const PRODUCT_COLUMN_IDX = 4;

const UUID_IDX = 0;
const CREATION_TIME_IDX = 1;
const UPDATE_TIME_IDX = 2;
const NAME_IDX = 3;
const TITLE_IDX = 4;
const URLS_IDX = 5;

function getProxy(): CommerceInternalsApiProxy {
  return CommerceInternalsApiProxy.getInstance();
}

function entryVerificationElement(value: boolean, expected: boolean) {
  const checkmarkHTML = getTrustedHTML`✔`;
  const crossmarkHTML = getTrustedHTML`✖`;
  const span = document.createElement('span');
  const isValid: boolean = value === expected;
  span.innerHTML = isValid ? checkmarkHTML : crossmarkHTML;
  span.classList.add(isValid ? 'eligible' : 'ineligible');
  return span;
}

function createLiElement(factor: string, entry: EligibleEntry) {
  const li = document.createElement('li');
  li.innerText = factor + (entry.value ? ': true ' : ': false ');
  li.appendChild(entryVerificationElement(entry.value, entry.expectedValue));
  return li;
}

function seeEligibleDetails() {
  getProxy().getShoppingListEligibleDetails().then(({detail}) => {
    const element = getRequiredElement('shopping-list-eligible-details');
    getRequiredElement('shopping-list-eligible-see-details-btn').innerText =
        'Refresh';
    while (element.hasChildNodes()) {
      element.removeChild(element.firstElementChild!);
    }

    const ul = document.createElement('ul');
    ul.appendChild(createLiElement(
        'IsRegionLockedFeatureEnabled', detail.isRegionLockedFeatureEnabled));
    ul.appendChild(createLiElement(
        'IsShoppingListAllowedForEnterprise',
        detail.isShoppingListAllowedForEnterprise));
    ul.appendChild(
        createLiElement('IsAccountCheckerValid', detail.isAccountCheckerValid));
    ul.appendChild(createLiElement('IsSignedIn', detail.isSignedIn));
    ul.appendChild(
        createLiElement('IsSyncingBookmarks', detail.isSyncingBookmarks));
    ul.appendChild(createLiElement(
        'IsAnonymizedUrlDataCollectionEnabled',
        detail.isAnonymizedUrlDataCollectionEnabled));
    ul.appendChild(createLiElement(
        'IsSubjectToParentalControls', detail.isSubjectToParentalControls));

    element.appendChild(ul);
  });
}

function initialize() {
  getRequiredElement('shopping-list-eligible-see-details-btn')
      .addEventListener('click', seeEligibleDetails);

  getRequiredElement('reset-price-tracking-email-pref-button')
      .addEventListener('click', () => {
        getProxy().resetPriceTrackingEmailPref();
      });

  const resetMessage =
      'All your product specification sets will be removed. Are you sure?';
  getRequiredElement('reset_product_specifications_button')
      .addEventListener('click', () => {
        if (confirm(resetMessage)) {
          getProxy().resetProductSpecifications();
          location.reload();
        }
      });

  getProxy().getCallbackRouter().onShoppingListEligibilityChanged.addListener(
      (eligible: boolean) => {
        updateShoppingListEligibleStatus(eligible);
      });

  getProxy().getIsShoppingListEligible().then(({eligible}) => {
    updateShoppingListEligibleStatus(eligible);
    if (eligible) {
      renderSubscriptions();
      renderProductSpecifications();
    }
  });
}

function renderSubscriptions() {
  getProxy().getSubscriptionDetails().then(({subscriptions}) => {
    if (!subscriptions || subscriptions.length == 0) {
      return;
    }

    const subscriptionsElement = document.getElementById('subscriptions');
    if (!subscriptionsElement) {
      return;
    }
    const table = document.createElement('table');
    const thead = document.createElement('thead');
    const tr = document.createElement('tr');

    for (const colName of SUBSCRIPTION_ROWS) {
      const th = document.createElement('th');
      th.innerText = colName;
      th.setAttribute('align', 'left');
      tr.appendChild(th);
    }
    thead.appendChild(tr);
    table.appendChild(thead);

    for (let i = 0; i < subscriptions.length; i++) {
      const productInfos = subscriptions[i]!.productInfos;

      // Highlight red if there are no bookmarks for the subscription.
      const row = createRow();
      if (productInfos.length == 0) {
        row.classList.add('error-row');
        row.setAttribute('bgcolor', 'FF7F7F');
        const columns = row.getElementsByTagName('td');
        columns[CLUSTER_ID_COLUMN_IDX]!.textContent =
            BigInt(subscriptions[i]!.clusterId).toString();
        table.appendChild(row);
        continue;
      }

      for (let j = 0; j < productInfos.length; j++) {
        const columns = row.getElementsByTagName('td');
        columns[CLUSTER_ID_COLUMN_IDX]!.textContent =
            BigInt(productInfos[j]!.info!.clusterId!).toString();
        columns[DOMAIN_COLUMN_IDX]!.textContent = productInfos[j]!.info.domain!;
        columns[CURRENT_PRICE_COLUMN_IDX]!.textContent =
            productInfos[j]!.info.currentPrice!;
        columns[PREVIOUS_PRICE_COLUMN_IDX]!.textContent =
            productInfos[j]!.info.previousPrice!;

        const url = productInfos[j]!.info.productUrl.url;
        const productCell = columns[PRODUCT_COLUMN_IDX]!;
        if (url == undefined) {
          productCell.textContent = productInfos[j]!.info.title!;
        } else {
          const a = document.createElement('a');
          a.textContent = productInfos[j]!.info.title!;
          a.setAttribute('href', url);
          productCell.appendChild(a);
        }
        const imageUrl = productInfos[j]?.info.imageUrl;
        if (imageUrl != undefined) {
          const space = document.createElement('span');
          space.textContent = ' ';
          productCell.appendChild(space);
          const imgLink = document.createElement('a');
          imgLink.textContent = '(image)';
          imgLink.setAttribute('href', imageUrl.url);
          productCell.appendChild(imgLink);
        }

        row.appendChild(productCell);
        table.appendChild(row);
        subscriptionsElement.appendChild(table);
      }
    }
  });
}

function renderProductSpecifications() {
  getProxy().getProductSpecificationsDetails().then(
      ({productSpecificationsSet}) => {
        if (!productSpecificationsSet || productSpecificationsSet.length == 0) {
          return;
        }

        const productSpecificationsElement =
            document.getElementById('product_specifications');
        if (!productSpecificationsElement) {
          return;
        }
        const table = document.createElement('table');
        const thead = document.createElement('thead');
        const tr = document.createElement('tr');

        for (const colName of PRODUCT_SPECIFICATIONS_ROWS) {
          const th = document.createElement('th');
          th.innerText = colName;
          th.setAttribute('align', 'left');
          tr.appendChild(th);
        }
        thead.appendChild(tr);
        table.appendChild(thead);

        for (let i = 0; i < productSpecificationsSet.length; i++) {
          const row = createProductSpecificationsRow();
          const columns = row.getElementsByTagName('td');
          columns[UUID_IDX]!.textContent = productSpecificationsSet[i]!.uuid;
          columns[CREATION_TIME_IDX]!.textContent =
              productSpecificationsSet[i]!.creationTime;
          columns[UPDATE_TIME_IDX]!.textContent =
              productSpecificationsSet[i]!.updateTime;
          columns[NAME_IDX]!.textContent = productSpecificationsSet[i]!.name;
          for (const urlInfo of productSpecificationsSet[i]!.urlInfos) {
            const divTitle = document.createElement('div');
            divTitle.textContent = urlInfo.title;
            divTitle.classList.add('product-specs-title');
            columns[TITLE_IDX]!.appendChild(divTitle);
            const divUrl = document.createElement('div');
            divUrl.textContent = urlInfo.url.url;
            columns[URLS_IDX]!.appendChild(divUrl);
          }
          table.appendChild(row);
        }
        productSpecificationsElement.appendChild(table);
      });
}

function createProductSpecificationsRow() {
  const uuidCell = document.createElement('td');
  const creationTimeCell = document.createElement('td');
  const updateTimeCell = document.createElement('td');
  const nameCell = document.createElement('td');
  const titleCell = document.createElement('td');
  const urlsCell = document.createElement('td');
  const row = document.createElement('tr');
  for (const cell
           of [uuidCell, creationTimeCell, updateTimeCell, nameCell, titleCell,
               urlsCell]) {
    cell.vAlign = 'top';
    row.appendChild(cell);
  }
  return row;
}

function createRow() {
  const clusterIdCell = document.createElement('td');
  const domainCell = document.createElement('td');
  const currentPriceCell = document.createElement('td');
  const previousPriceCell = document.createElement('td');
  const productCell = document.createElement('td');
  const row = document.createElement('tr');
  for (const cell
           of [clusterIdCell, domainCell, currentPriceCell, previousPriceCell,
               productCell]) {
    row.appendChild(cell);
  }
  return row;
}

function updateShoppingListEligibleStatus(eligible: boolean) {
  const eligibleText: string = eligible ? 'true' : 'false';
  const element = getRequiredElement('shopping-list-eligible');
  element.classList.remove('eligible');
  element.classList.remove('ineligible');
  element.innerText = eligibleText;
  element.classList.add(eligible ? 'eligible' : 'ineligible');
}

document.addEventListener('DOMContentLoaded', initialize);