Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/elements/framework/mr-star/mr-star.js b/static_src/elements/framework/mr-star/mr-star.js
new file mode 100644
index 0000000..fe509be
--- /dev/null
+++ b/static_src/elements/framework/mr-star/mr-star.js
@@ -0,0 +1,235 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {LitElement, html, css} from 'lit-element';
+
+/**
+ * `<mr-star>`
+ *
+ * A button for starring a resource. Does not directly integrate with app
+ * state. Subclasses by <mr-issue-star> and <mr-project-star>, which add
+ * resource-specific logic for state management.
+ *
+ */
+export class MrStar extends LitElement {
+  /** @override */
+  static get styles() {
+    return css`
+      :host {
+        display: block;
+        --mr-star-size: var(--chops-icon-font-size);
+      }
+      button {
+        background: none;
+        border: none;
+        cursor: pointer;
+        padding: 0;
+        margin: 0;
+        display: flex;
+        align-items: center;
+      }
+      /* TODO(crbug.com/monorail/8008): Add nicer looking loading style. */
+      button.loading {
+        opacity: 0.5;
+        cursor: default;
+      }
+      i.material-icons {
+        font-size: var(--mr-star-size);
+        color: var(--chops-primary-icon-color);
+      }
+      i.material-icons.starred {
+        color: var(--chops-primary-accent-color);
+      }
+    `;
+  }
+
+  /** @override */
+  render() {
+    const {isStarred} = this;
+    return html`
+      <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+      <button class="star-button"
+        @click=${this._loginOrStar}
+        title=${this._starToolTip}
+        role="checkbox"
+        aria-checked=${isStarred ? 'true' : 'false'}
+        class=${this.requesting ? 'loading' : ''}
+      >
+        ${isStarred ? html`
+          <i class="material-icons starred" role="presentation">
+            star
+          </i>
+        `: html`
+          <i class="material-icons" role="presentation">
+            star_border
+          </i>
+        `}
+      </button>
+    `;
+  }
+
+  /** @override */
+  static get properties() {
+    return {
+      /**
+       * Note: In order for re-renders to happen based on the getters defined
+       * in this class, those getters must have values based on properties.
+       * Subclasses of <mr-star> are not expected to inherit <mr-star>'s
+       * properties, but they should make sure their getter implementations
+       * are also backed by properties.
+       */
+      _isStarred: {type: Boolean},
+      _isLoggedIn: {type: Boolean},
+      _canStar: {type: Boolean},
+      _requesting: {type: Boolean},
+    };
+  }
+
+  /** @override */
+  constructor() {
+    super();
+    /**
+     * @type {boolean} Whether the user has starred the resource or not.
+     */
+    this._isStarred = false;
+
+    /**
+     * @type {boolean} If the user is logged in.
+     */
+    this._isLoggedIn = false;
+
+    /**
+     * @return {boolean} Whether the user has permission to star the star.
+     */
+    this._canStar = true;
+
+    /**
+     * @return {boolean} Whether there's an in-flight request to star
+     * the resource.
+     */
+    this._requesting = false;
+  }
+
+  /** @override */
+  connectedCallback() {
+    super.connectedCallback();
+
+    // Prevent clicks on this element from causing navigation if the element
+    // is embedded inside a link.
+    this.addEventListener('click', (e) => e.preventDefault());
+  }
+
+  /**
+   * @return {boolean} If the user is logged in.
+   */
+  get isLoggedIn() {
+    return this._isLoggedIn;
+  }
+
+  /**
+   * @return {boolean} If there's an in-flight request that might affect the
+   *   star's data.
+   */
+  get requesting() {
+    return this._requesting;
+  }
+
+  /**
+   * @return {boolean} Whether the resource is starred or not.
+   */
+  get isStarred() {
+    return this._isStarred;
+  }
+
+  /**
+   * @return {boolean} If the user has permission to star.
+   */
+  get canStar() {
+    return this._canStar;
+  }
+
+  /**
+   * @return {boolean}
+   */
+  get _starringEnabled() {
+    return this.isLoggedIn && this.canStar && !this.requesting;
+  }
+
+  /**
+   * @return {string} The name of the resource kind being starred.
+   * ie: issue, project, etc.
+   */
+  get type() {
+    return 'resource';
+  }
+
+  /**
+   * @return {string} the title to display on the star button.
+   */
+  get _starToolTip() {
+    if (!this.isLoggedIn) {
+      return `Login to star this ${this.type}.`;
+    }
+    if (!this.canStar) {
+      return `You don't have permission to star this ${this.type}.`;
+    }
+    if (this.requesting) {
+      return `Loading star state for this ${this.type}.`;
+    }
+    return `${this.isStarred ? 'Unstar' : 'Star'} this ${this.type}.`;
+  }
+
+  /**
+   * Logins the user if they're not logged in. Otherwise, stars or
+   * unstars the resource based on star state.
+   */
+  _loginOrStar() {
+    if (!this.isLoggedIn) {
+      this.login();
+    } else {
+      this.toggleStar();
+    }
+  }
+
+  /**
+   * Logs in the user.
+   */
+  login() {
+    // TODO(crbug.com/monorail/6073): Replace this logic with a function call
+    // when moving authentication to frontend.
+    // HACK: In our current login implementation, login URLs can only be
+    // generated by the backend which makes piping a login URL into a component
+    // a <mr-star> complex. To get around this, we're using the
+    // legacy window.CS_env infrastructure.
+    window.location.href = window.CS_env.login_url;
+  }
+
+  /**
+   * Stars or unstars the resource based on the user's interaction.
+   */
+  toggleStar() {
+    if (!this._starringEnabled) return;
+    if (this.isStarred) {
+      this.unstar();
+    } else {
+      this.star();
+    }
+  }
+
+  /**
+   * Stars the given resource. To be implemented by a subclass.
+   */
+  star() {
+    throw new Error('Method not implemented.');
+  }
+
+  /**
+   * Unstars the given resource. To be implemented by a subclass.
+   */
+  unstar() {
+    throw new Error('Method not implemented.');
+  }
+}
+
+customElements.define('mr-star', MrStar);