chromium/chrome/browser/resources/ash/settings/os_about_page/edit_hostname_dialog.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview 'edit-hostname-dialog' is a component allowing the
 * user to edit the device hostname.
 */
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/ash/common/cr_elements/cr_radio_button/cr_radio_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_radio_group/cr_radio_group.js';
import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
import '../settings_shared.css.js';

import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './device_name_browser_proxy.js';
import {SetDeviceNameResult} from './device_name_util.js';
import {getTemplate} from './edit_hostname_dialog.html.js';

const MAX_INPUT_LENGTH = 15;

const MIN_INPUT_LENGTH = 1;

const UNALLOWED_CHARACTERS = '[^0-9A-Za-z-]+';

const EMOJI_REGEX_EXP =
    /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gi;

export interface EditHostnameDialogElement {
  $: {
    dialog: CrDialogElement,
  };
}

const EditHostnameDialogElementBase = I18nMixin(PolymerElement);

export class EditHostnameDialogElement extends EditHostnameDialogElementBase {
  static get is() {
    return 'edit-hostname-dialog' as const;
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      deviceName_: {
        type: String,
        value: '',
        observer: 'onDeviceNameChanged_',
      },

      isInputInvalid_: {
        type: Boolean,
        value: false,
        reflectToAttribute: true,
      },

      inputCountString_: {
        type: String,
        computed: 'computeInputCountString_(deviceName_)',
      },
    };
  }

  private deviceName_: string;
  private isInputInvalid_: boolean;
  private inputCountString_: string;

  private deviceNameBrowserProxy_: DeviceNameBrowserProxy;

  constructor() {
    super();

    this.deviceNameBrowserProxy_ = DeviceNameBrowserProxyImpl.getInstance();
  }

  /**
   * Returns a formatted string containing the current number of characters
   * entered in the input compared to the maximum number of characters allowed.
   */
  private computeInputCountString_(): string {
    return this.i18n(
        'aboutDeviceNameInputCharacterCount',
        this.deviceName_.length.toLocaleString(),
        MAX_INPUT_LENGTH.toLocaleString());
  }

  private onCancelClick_(): void {
    this.$.dialog.close();
  }

  /**
   * Observer for deviceName_ that sanitizes its value by removing any
   * Emojis and truncating it to MAX_INPUT_LENGTH. This method will be
   * recursively called until deviceName_ is fully sanitized.
   */
  private onDeviceNameChanged_(_newValue: string, oldValue: string): void {
    if (oldValue) {
      const sanitizedOldValue = oldValue.replace(EMOJI_REGEX_EXP, '');
      // If sanitizedOldValue.length > MAX_INPUT_LENGTH, the user attempted to
      // enter more than the max limit, this method was called and it was
      // truncated, and then this method was called one more time.
      this.isInputInvalid_ = sanitizedOldValue.length > MAX_INPUT_LENGTH;
    } else {
      this.isInputInvalid_ = false;
    }

    // Remove all Emojis from the name.
    const sanitizedDeviceName = this.deviceName_.replace(EMOJI_REGEX_EXP, '');

    // Truncate the name to MAX_INPUT_LENGTH.
    this.deviceName_ = sanitizedDeviceName.substring(0, MAX_INPUT_LENGTH);

    if (this.deviceName_.length < MIN_INPUT_LENGTH ||
        this.deviceName_.match(UNALLOWED_CHARACTERS)) {
      this.isInputInvalid_ = true;
    }
  }

  private onDoneClick_(): void {
    this.deviceNameBrowserProxy_.attemptSetDeviceName(this.deviceName_)
        .then(result => {
          this.handleSetDeviceNameResponse_(result);
        });
    this.$.dialog.close();
  }

  private handleSetDeviceNameResponse_(result: SetDeviceNameResult): void {
    if (result !== SetDeviceNameResult.UPDATE_SUCCESSFUL) {
      console.error('ERROR IN UPDATE', result);
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [EditHostnameDialogElement.is]: EditHostnameDialogElement;
  }
}

customElements.define(EditHostnameDialogElement.is, EditHostnameDialogElement);