Add new component to edit background color in the sidebar with custom
color support

Change-Id: Id238673fea57dbd9768e5dd5e33c8541810f48d9
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/703500
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/skottie/modules/skottie-background-settings-sk/BUILD.bazel b/skottie/modules/skottie-background-settings-sk/BUILD.bazel
new file mode 100644
index 0000000..5495d03f
--- /dev/null
+++ b/skottie/modules/skottie-background-settings-sk/BUILD.bazel
@@ -0,0 +1,18 @@
+load("//infra-sk:index.bzl", "sk_element")
+
+sk_element(
+    name = "skottie-background-settings-sk",
+    sass_deps = ["//elements-sk/modules:colors_sass_lib"],
+    sass_srcs = ["skottie-background-settings-sk.scss"],
+    sk_element_deps = ["//skottie/modules/skottie-dropdown-sk"],
+    ts_deps = [
+        "//elements-sk/modules:define_ts_lib",
+        "//infra-sk/modules/ElementSk:index_ts_lib",
+        "@npm//lit-html",
+    ],
+    ts_srcs = [
+        "index.ts",
+        "skottie-background-settings-sk.ts",
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/skottie/modules/skottie-background-settings-sk/index.ts b/skottie/modules/skottie-background-settings-sk/index.ts
new file mode 100644
index 0000000..3de74fe
--- /dev/null
+++ b/skottie/modules/skottie-background-settings-sk/index.ts
@@ -0,0 +1 @@
+import './skottie-background-settings-sk';
diff --git a/skottie/modules/skottie-background-settings-sk/skottie-background-settings-sk.scss b/skottie/modules/skottie-background-settings-sk/skottie-background-settings-sk.scss
new file mode 100644
index 0000000..2b8ec84
--- /dev/null
+++ b/skottie/modules/skottie-background-settings-sk/skottie-background-settings-sk.scss
@@ -0,0 +1,32 @@
+@import '../../../elements-sk/modules/colors';
+
+skottie-background-settings-sk {
+  .wrapper {
+    width: 100%;
+    padding: 8px;
+  }
+
+  .color-form {
+    margin-top: 16px;
+
+    &--color {
+      border: 1px solid var(--skottie-on-surface-white);
+      border-right: none;
+      border-radius: 4px 0 0 4px;
+      padding: 4px;
+
+      input {
+        border: none;
+        width: 18px;
+        padding: 6px 0 0 0;
+      }
+    }
+
+    &--opacity {
+      border: 1px solid var(--skottie-on-surface-white);
+      border-radius: 0 4px 4px 0;
+      padding: 4px;
+      width: 80px;
+    }
+  }
+}
diff --git a/skottie/modules/skottie-background-settings-sk/skottie-background-settings-sk.ts b/skottie/modules/skottie-background-settings-sk/skottie-background-settings-sk.ts
new file mode 100644
index 0000000..2cf4d2f
--- /dev/null
+++ b/skottie/modules/skottie-background-settings-sk/skottie-background-settings-sk.ts
@@ -0,0 +1,175 @@
+/**
+ * @module skottie-background-settings-sk
+ * @description <h2><code>skottie-background-settings-sk</code></h2>
+ *
+ * <p>
+ *   A component to edit the background color in the sidebar
+ * </p>
+ *
+ *
+ * @evt background-change - This event is triggered every time the
+ *      user selects a new color or opacity
+ *
+ */
+import { define } from '../../../elements-sk/modules/define';
+import { html, TemplateResult } from 'lit-html';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import '../skottie-dropdown-sk';
+import { DropdownSelectEvent } from '../skottie-dropdown-sk/skottie-dropdown-sk';
+
+type BackgroundMode = 'light' | 'dark' | 'custom';
+
+type ValuePair = {
+  color: string;
+  opacity: number;
+};
+
+const values: Record<BackgroundMode, ValuePair> = {
+  light: {
+    color: '#FFFFFF',
+    opacity: 100,
+  },
+  dark: {
+    color: '#000000',
+    opacity: 100,
+  },
+  custom: {
+    color: '#FF0000',
+    opacity: 100,
+  },
+};
+
+export interface SkottieBackgroundSettingsEventDetail {
+  color: string;
+  opacity: number;
+}
+
+export class SkottieBackgroundSettingsSk extends ElementSk {
+  private _color: string = values.light.color;
+
+  private _opacity: number = values.light.opacity;
+
+  private _backgroundMode: BackgroundMode = 'light';
+
+  private static template = (ele: SkottieBackgroundSettingsSk) => html`
+    <div class="wrapper">
+      <skottie-dropdown-sk
+        id="background-selector"
+        .name="background-selector"
+        .options=${[
+          {
+            id: 'light',
+            value: 'Light',
+            selected: ele._backgroundMode === 'light',
+          },
+          {
+            id: 'dark',
+            value: 'Dark',
+            selected: ele._backgroundMode === 'dark',
+          },
+          {
+            id: 'custom',
+            value: 'Custom',
+            selected: ele._backgroundMode === 'custom',
+          },
+        ]}
+        @select=${ele.backgroundSelectHandler}
+        border
+      ></skottie-dropdown-sk>
+      ${ele.renderColorInput()}
+    </div>
+  `;
+
+  constructor() {
+    super(SkottieBackgroundSettingsSk.template);
+  }
+
+  connectedCallback(): void {
+    super.connectedCallback();
+    this._render();
+    // Query params are not set when `connectedCallback` is called.
+    // As a workaround this timeout of 1ms seems to be enough.
+    setTimeout(() => {
+      const params = new URL(document.location.href).searchParams;
+      const color = params.has('bg') ? params.get('bg')! : '';
+      if (color) {
+        let mode: BackgroundMode;
+        if (color === values.light.color) {
+          mode = 'light';
+        } else if (color === values.dark.color) {
+          mode = 'dark';
+        } else {
+          mode = 'custom';
+        }
+        this._backgroundMode = mode;
+        this._opacity = values[this._backgroundMode].opacity;
+        this._color = color;
+        this._render();
+        this._submit();
+      }
+    }, 1);
+  }
+
+  private renderColorInput(): TemplateResult | null {
+    if (this._backgroundMode === 'custom') {
+      return html`
+        <div class="color-form">
+          <label class="color-form--color">
+            <input
+              type="color"
+              value=${this._color}
+              @change=${this.onColorChange}
+            />
+            <span>${this._color}</span>
+          </label>
+          <input
+            type="number"
+            class="color-form--opacity"
+            .value=${this._opacity}
+            @change=${this.onOpacityChange}
+          />
+        </div>
+      `;
+    }
+    return null;
+  }
+
+  private _submit(): void {
+    this.dispatchEvent(
+      new CustomEvent<SkottieBackgroundSettingsEventDetail>(
+        'background-change',
+        {
+          detail: {
+            color: this._color,
+            opacity: this._opacity,
+          },
+          bubbles: true,
+        }
+      )
+    );
+  }
+
+  private backgroundSelectHandler(ev: CustomEvent<DropdownSelectEvent>): void {
+    this._backgroundMode = ev.detail.value as BackgroundMode;
+    this._color = values[this._backgroundMode].color;
+    this._opacity = values[this._backgroundMode].opacity;
+    this._submit();
+    this._render();
+  }
+
+  private onColorChange(ev: Event): void {
+    const input = ev.target as HTMLInputElement;
+    this._color = input.value;
+    this._submit();
+    this._render();
+  }
+
+  private onOpacityChange(ev: Event): void {
+    const input = ev.target as HTMLInputElement;
+    this._opacity = input.valueAsNumber;
+    this._submit();
+    this._render();
+  }
+}
+
+define('skottie-background-settings-sk', SkottieBackgroundSettingsSk);
diff --git a/skottie/modules/skottie-player-sk/skottie-player-sk.ts b/skottie/modules/skottie-player-sk/skottie-player-sk.ts
index 6d0a54a..1b43f27 100644
--- a/skottie/modules/skottie-player-sk/skottie-player-sk.ts
+++ b/skottie/modules/skottie-player-sk/skottie-player-sk.ts
@@ -265,6 +265,11 @@
     );
   }
 
+  getBackgroundColor(): string {
+    const params = new URL(document.location.href).searchParams;
+    return params.has('bg') ? params.get('bg')! : '#fff';
+  }
+
   connectedCallback(): void {
     super.connectedCallback();
     const params = new URL(document.location.href).searchParams;
@@ -275,7 +280,7 @@
       ? +this.getAttribute('height')!
       : 256;
     this.showControls = params.has('controls');
-    this.bgColor = params.has('bg') ? params.get('bg')! : '#fff';
+    this.bgColor = this.getBackgroundColor();
     this._render();
   }
 
@@ -284,6 +289,8 @@
     this.height = config.height;
     this.renderFPS = config.fps;
     this._animationName = config.lottie.nm as string;
+    const params = new URL(document.location.href).searchParams;
+    this.bgColor = this.getBackgroundColor();
 
     this._render();
     return canvasReady.then((ck: CanvasKit) => {
diff --git a/skottie/modules/skottie-sk/BUILD.bazel b/skottie/modules/skottie-sk/BUILD.bazel
index 9c0bba8..d6d4d78 100644
--- a/skottie/modules/skottie-sk/BUILD.bazel
+++ b/skottie/modules/skottie-sk/BUILD.bazel
@@ -54,6 +54,7 @@
         "//skottie/modules/skottie-file-settings-sk",
         "//elements-sk/modules/icons/file-download-icon-sk",
         "//skottie/modules/skottie-file-form-sk",
+        "//skottie/modules/skottie-background-settings-sk",
     ],
     ts_deps = [
         "//infra-sk/modules/ElementSk:index_ts_lib",
diff --git a/skottie/modules/skottie-sk/skottie-sk.ts b/skottie/modules/skottie-sk/skottie-sk.ts
index cd61596..9b5efde 100644
--- a/skottie/modules/skottie-sk/skottie-sk.ts
+++ b/skottie/modules/skottie-sk/skottie-sk.ts
@@ -29,7 +29,7 @@
 import { SoundMap, AudioPlayer } from '../audio';
 import '../skottie-performance-sk';
 import { renderByDomain } from '../helpers/templates';
-import { supportedDomains } from '../helpers/domains';
+import { isDomain, supportedDomains } from '../helpers/domains';
 import '../skottie-audio-sk';
 import { ElementSk } from '../../../infra-sk/modules/ElementSk';
 import {
@@ -77,6 +77,8 @@
 } from '../skottie-file-settings-sk/skottie-file-settings-sk';
 import '../skottie-file-form-sk';
 import { SkottieFilesEventDetail } from '../skottie-file-form-sk/skottie-file-form-sk';
+import '../skottie-background-settings-sk';
+import { SkottieBackgroundSettingsEventDetail } from '../skottie-background-settings-sk/skottie-background-settings-sk';
 
 // It is assumed that this symbol is being provided by a version.js file loaded in before this
 // file.
@@ -118,7 +120,7 @@
   supportedDomains.LOCALHOST,
 ];
 
-type UIMode = 'dialog' | 'loading' | 'loaded';
+type UIMode = 'dialog' | 'loading' | 'loaded' | 'idle';
 
 const caption = (text: string, mode: ViewMode) => {
   if (mode === 'presentation') {
@@ -208,6 +210,8 @@
       default:
       case 'dialog':
         return this.displayDialog();
+      case 'idle':
+        return this.displayIdle();
       case 'loading':
         return displayLoading();
       case 'loaded':
@@ -227,6 +231,14 @@
     ></skottie-config-sk>
   `;
 
+  private displayIdle = () => html`
+    <div class="threecol">
+      <div class="left">${this.leftControls()}</div>
+      <div class="main"></div>
+      <div class="right">${this.rightControls()}</div>
+    </div>
+  `;
+
   private displayLoaded = () => html`
     <div class="threecol">
       <div class="left">${this.leftControls()}</div>
@@ -307,8 +319,8 @@
     return html`
       <details class="embed expando">
         <summary id="embed-open">
-          <span>Embed</span><expand-less-icon-sk></expand-less-icon-sk
-          ><expand-more-icon-sk></expand-more-icon-sk>
+          <span>Embed</span><expand-less-icon-sk></expand-less-icon-sk>
+          <expand-more-icon-sk></expand-more-icon-sk>
         </summary>
         <label>
           Embed using an iframe
@@ -348,7 +360,8 @@
         ></skottie-file-form-sk>
       </div>
 
-      ${this.fileSettingsDialog()} ${this.audioDialog()} ${this.optionsDialog()}
+      ${this.fileSettingsDialog()} ${this.backgroundDialog()}
+      ${this.audioDialog()} ${this.optionsDialog()}
 
       <button
         class="apply-button"
@@ -390,8 +403,8 @@
   private optionsDialog = () => html`
     <details class="expando">
       <summary id="options-open">
-        <span>Options</span><expand-less-icon-sk></expand-less-icon-sk
-        ><expand-more-icon-sk></expand-more-icon-sk>
+        <span>Options</span><expand-less-icon-sk></expand-less-icon-sk>
+        <expand-more-icon-sk></expand-more-icon-sk>
       </summary>
       <div class="options-container">
         <checkbox-sk
@@ -414,8 +427,8 @@
             this.toggleAudio((e.target! as HTMLDetailsElement).open)}
         >
           <summary id="audio-open">
-            <span>Audio</span><expand-less-icon-sk></expand-less-icon-sk
-            ><expand-more-icon-sk></expand-more-icon-sk>
+            <span>Audio</span><expand-less-icon-sk></expand-less-icon-sk>
+            <expand-more-icon-sk></expand-more-icon-sk>
           </summary>
 
           <skottie-audio-sk
@@ -437,8 +450,8 @@
           this.toggleFileSettings((e.target! as HTMLDetailsElement).open)}
       >
         <summary id="fileSettings-open">
-          <span>File Settings</span><expand-less-icon-sk></expand-less-icon-sk
-          ><expand-more-icon-sk></expand-more-icon-sk>
+          <span>File Settings</span><expand-less-icon-sk></expand-less-icon-sk>
+          <expand-more-icon-sk></expand-more-icon-sk>
         </summary>
         <skottie-file-settings-sk
           .width=${this.width}
@@ -449,6 +462,25 @@
       </details>
     `;
 
+  private backgroundDialog = () =>
+    html`
+      <details
+        class="expando"
+        ?open=${this.showBackgroundSettings}
+        @toggle=${(e: Event) =>
+          this.toggleBackgroundSettings((e.target! as HTMLDetailsElement).open)}
+      >
+        <summary>
+          <span>Background color</span>
+          <expand-less-icon-sk></expand-less-icon-sk>
+          <expand-more-icon-sk></expand-more-icon-sk>
+        </summary>
+        <skottie-background-settings-sk
+          @background-change=${this.skottieBackgroundUpdated}
+        ></skottie-background-settings-sk>
+      </details>
+    `;
+
   private iframeDirections = () =>
     `<iframe width="${this.width}" height="${this.height}" src="${window.location.origin}/e/${this.hash}?w=${this.width}&h=${this.height}" scrolling=no>`;
 
@@ -485,8 +517,8 @@
       this.toggleLibrary((e.target! as HTMLDetailsElement).open)}
   >
     <summary id="library-open">
-      <span>Library</span><expand-less-icon-sk></expand-less-icon-sk
-      ><expand-more-icon-sk></expand-more-icon-sk>
+      <span>Library</span><expand-less-icon-sk></expand-less-icon-sk>
+      <expand-more-icon-sk></expand-more-icon-sk>
     </summary>
 
     <skottie-library-sk @select=${this.updateAnimation}> </skottie-library-sk>
@@ -540,8 +572,8 @@
         this.toggleTextEditor((e.target! as HTMLDetailsElement).open)}
     >
       <summary id="edit-text-open">
-        <span>Edit Text</span><expand-less-icon-sk></expand-less-icon-sk
-        ><expand-more-icon-sk></expand-more-icon-sk>
+        <span>Edit Text</span><expand-less-icon-sk></expand-less-icon-sk>
+        <expand-more-icon-sk></expand-more-icon-sk>
       </summary>
 
       <skottie-text-editor-sk
@@ -561,8 +593,8 @@
         this.toggleShaderEditor((e.target! as HTMLDetailsElement).open)}
     >
       <summary>
-        <span>Edit Shader</span><expand-less-icon-sk></expand-less-icon-sk
-        ><expand-more-icon-sk></expand-more-icon-sk>
+        <span>Edit Shader</span><expand-less-icon-sk></expand-less-icon-sk>
+        <expand-more-icon-sk></expand-more-icon-sk>
       </summary>
 
       <skottie-shader-editor-sk
@@ -651,6 +683,8 @@
 
   private showFileSettings: boolean = false;
 
+  private showBackgroundSettings: boolean = false;
+
   private skottieLibrary: SkottieLibrarySk | null = null;
 
   private skottiePlayer: SkottiePlayerSk | null = null;
@@ -661,7 +695,7 @@
 
   private stateChanged: () => void;
 
-  private ui: UIMode = 'dialog';
+  private ui: UIMode = 'idle';
 
   private viewMode: ViewMode = 'default';
 
@@ -694,6 +728,7 @@
         bg: this.backgroundColor,
         mode: this.viewMode,
         fs: this.showFileSettings,
+        b: this.showBackgroundSettings,
       }),
       /* setState */ (newState) => {
         this.showLottie = !!newState.l;
@@ -708,6 +743,7 @@
         this.height = +newState.h;
         this.fps = +newState.f;
         this.showFileSettings = !!newState.fs;
+        this.showBackgroundSettings = !!newState.b;
         this.viewMode =
           newState.mode === 'presentation' ? 'presentation' : 'default';
         this.backgroundColor = String(newState.bg);
@@ -867,12 +903,14 @@
     this.width = e.detail.width;
     this.height = e.detail.height;
     this.fps = e.detail.fps;
-    this.autoSize();
     this.stateChanged();
+    if (this.state.lottie) {
+      this.autoSize();
+      this.initializePlayer();
+      // Re-sync all players
+      this.rewind();
+    }
     this.render();
-    this.initializePlayer();
-    // Re-sync all players
-    this.rewind();
   }
 
   private skottieFilesSelected(e: CustomEvent<SkottieFilesEventDetail>) {
@@ -890,6 +928,22 @@
     this.upload();
   }
 
+  private skottieBackgroundUpdated(
+    e: CustomEvent<SkottieBackgroundSettingsEventDetail>
+  ) {
+    const background = e.detail;
+    this.backgroundColor = background.color;
+    this.stateChanged();
+    if (this.state.lottie) {
+      this.autoSize();
+      this.initializePlayer();
+      // Re-sync all players
+      this.rewind();
+    }
+
+    this.render();
+  }
+
   private selectionCancelled() {
     this.ui = 'loaded';
     this.render();
@@ -1099,7 +1153,9 @@
     errorMessage(msg);
     console.error(msg);
     window.history.pushState(null, '', '/');
-    this.ui = 'dialog';
+    // For development we recover to the loaded state to see the animation
+    // even if the upload didn't work
+    this.ui = isDomain(supportedDomains.LOCALHOST) ? 'loaded' : 'idle';
     this.render();
   }
 
@@ -1131,7 +1187,6 @@
           }
           this.ui = 'loaded';
           this.loadAssetsAndRender().then(() => {
-            console.log('loaded');
             this.dispatchEvent(
               new CustomEvent('initial-animation-loaded', { bubbles: true })
             );
@@ -1497,6 +1552,12 @@
     this.render();
   }
 
+  private toggleBackgroundSettings(open: boolean): void {
+    this.showBackgroundSettings = open;
+    this.stateChanged();
+    this.render();
+  }
+
   private toggleLottie(e: Event): void {
     // avoid double toggles
     e.preventDefault();