Add clipboard-sk element.
Displays a copy-content icon and when clicked copies the contents of the 'value' attribute into the user's clipboard. Also displays a tooltip letting the user know the value was copied.
If the value to be copied is expensive to calculate then compute the value in the `calculatedValue` function. See `clipboard-sk-demo.ts` for an example.
Bug: skia:13929
Change-Id: I2e385760f289d4184c642316152acdbcefef291e
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/607865
Auto-Submit: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Ravi Mistry <rmistry@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/infra-sk/modules/clipboard-sk/BUILD.bazel b/infra-sk/modules/clipboard-sk/BUILD.bazel
new file mode 100644
index 0000000..c0700be
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/BUILD.bazel
@@ -0,0 +1,56 @@
+load("//infra-sk:index.bzl", "karma_test", "sk_demo_page_server", "sk_element", "sk_element_puppeteer_test", "sk_page")
+
+sk_demo_page_server(
+ name = "demo_page_server",
+ sk_page = ":clipboard-sk-demo",
+)
+
+sk_element(
+ name = "clipboard-sk",
+ sass_srcs = ["clipboard-sk.scss"],
+ sk_element_deps = ["//infra-sk/modules/tooltip-sk"],
+ ts_deps = [
+ "//infra-sk/modules/ElementSk:index_ts_lib",
+ "@npm//elements-sk",
+ "@npm//lit-html",
+ "@npm//common-sk",
+ ],
+ ts_srcs = [
+ "clipboard-sk.ts",
+ "index.ts",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+sk_page(
+ name = "clipboard-sk-demo",
+ html_file = "clipboard-sk-demo.html",
+ scss_entry_point = "clipboard-sk-demo.scss",
+ sk_element_deps = [":clipboard-sk"],
+ ts_deps = ["@npm//common-sk"],
+ ts_entry_point = "clipboard-sk-demo.ts",
+)
+
+sk_element_puppeteer_test(
+ name = "clipboard-sk_puppeteer_test",
+ src = "clipboard-sk_puppeteer_test.ts",
+ sk_demo_page_server = ":demo_page_server",
+ deps = [
+ "//puppeteer-tests:util_ts_lib",
+ "@npm//@types/chai",
+ "@npm//chai",
+ ],
+)
+
+karma_test(
+ name = "clipboard-sk_test",
+ src = "clipboard-sk_test.ts",
+ deps = [
+ ":clipboard-sk",
+ "//infra-sk/modules:test_util_ts_lib",
+ "//infra-sk/modules/tooltip-sk",
+ "@npm//@types/chai",
+ "@npm//chai",
+ "@npm//common-sk",
+ ],
+)
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk-demo.html b/infra-sk/modules/clipboard-sk/clipboard-sk-demo.html
new file mode 100644
index 0000000..3bf944e
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk-demo.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>clipboard-sk</title>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ </head>
+ <body class="body-sk darkmode">
+ <h1>clipboard-sk</h1>
+ <div>
+ Hover over the icon
+ <clipboard-sk value="This gets copied to the clipboard"></clipboard-sk>
+ </div>
+ <h1>calculated clipboard-sk value</h1>
+ <div>
+ The value copied to the clipboard is calculated on the fly.
+ <clipboard-sk
+ id="onthefly"
+ value="This gets changed before copying to the clipboard"
+ ></clipboard-sk>
+ </div>
+ </body>
+</html>
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk-demo.scss b/infra-sk/modules/clipboard-sk/clipboard-sk-demo.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk-demo.scss
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk-demo.ts b/infra-sk/modules/clipboard-sk/clipboard-sk-demo.ts
new file mode 100644
index 0000000..935a2b5
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk-demo.ts
@@ -0,0 +1,9 @@
+import { $$ } from 'common-sk/modules/dom';
+import { ClipboardSk } from './clipboard-sk';
+import './index';
+
+// If a clipboard value is too expensive to calculate all the time, for example,
+// a CSV file, then you can set the `calculatedValue` property on the
+// clipboard-sk element and that will only be called if the user actually clicks
+// on the element.
+$$<ClipboardSk>('#onthefly')!.calculatedValue = async (): Promise<string> => 'This is the altered value.';
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk.scss b/infra-sk/modules/clipboard-sk/clipboard-sk.scss
new file mode 100644
index 0000000..51fabf8
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk.scss
@@ -0,0 +1,6 @@
+clipboard-sk {
+ content-copy-icon-sk svg.icon-sk-svg {
+ width: 16px;
+ height: 16px;
+ }
+}
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk.ts b/infra-sk/modules/clipboard-sk/clipboard-sk.ts
new file mode 100644
index 0000000..94ecc58
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk.ts
@@ -0,0 +1,89 @@
+/**
+ * @module modules/clipboard-sk
+ * @description <h2><code>clipboard-sk</code></h2>
+ *
+ * Displays a copy-content icon and when clicked copies the contents of the
+ * 'value' attribute into the user's clipboard. Also displays a tooltip letting
+ * the user know the value was copied.
+ *
+ * If the value to be copied is expensive to calculate then compute the value in
+ * the `calculatedValue` function. See `clipboard-sk-demo.ts` for an example.
+ *
+ * @attr value - The content to put into the clipboard.
+ *
+ */
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { $$ } from 'common-sk/modules/dom';
+import { ElementSk } from '../ElementSk';
+
+import 'elements-sk/icon/content-copy-icon-sk';
+import '../tooltip-sk';
+import { TooltipSk } from '../tooltip-sk/tooltip-sk';
+
+export const defaultToolTipMessage = 'Copy to clipboard';
+
+export const copyCompleteToolTipMessage = 'Copied!';
+
+export const copyFailedToolTipMessage = 'Failed to copy!';
+
+export class ClipboardSk extends ElementSk {
+ // We need to assign an id to the content-copy-icon-sk, so that the tooltip-sk
+ // has something to use as a target.
+ private icon_id: string = `x${`${Math.random()}`.slice(2)}`;
+
+ private tooltip: TooltipSk | null = null;
+
+ /** If the value to be copied is expensive to calculate then compute the value
+ * in the `calculatedValue` function. See `clipboard-sk-demo.ts` for an
+ * example.
+ * */
+ calculatedValue: (()=> Promise<string>) | null = null;
+
+ constructor() {
+ super(ClipboardSk.template);
+ }
+
+ private static template = (ele: ClipboardSk) => html`
+ <content-copy-icon-sk
+ id=${ele.icon_id}
+ @click=${() => ele.copyToClipboard()}>
+ @mouseleave=${() => ele.restoreToolTipMessage()}
+ </content-copy-icon-sk>
+ <tooltip-sk
+ target=${ele.icon_id}
+ value=${defaultToolTipMessage}>
+ </tooltip-sk>`;
+
+ connectedCallback(): void {
+ super.connectedCallback();
+ this._upgradeProperty('value');
+ this._render();
+ this.tooltip = $$('tooltip-sk', this);
+ }
+
+ private async copyToClipboard(): Promise<void> {
+ try {
+ if (this.calculatedValue !== null) {
+ this.value = await this.calculatedValue();
+ }
+ await navigator.clipboard.writeText(this.value);
+ this.tooltip!.value = copyCompleteToolTipMessage;
+ } catch (error) {
+ this.tooltip!.value = copyFailedToolTipMessage;
+ }
+ this._render();
+ }
+
+ private restoreToolTipMessage(): void {
+ this.tooltip!.value = defaultToolTipMessage;
+ this._render();
+ }
+
+ /** @prop value {string} The content to put into the clipboard. */
+ get value(): string { return this.getAttribute('value') || ''; }
+
+ set value(val: string) { this.setAttribute('value', val); }
+}
+
+define('clipboard-sk', ClipboardSk);
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk_puppeteer_test.ts b/infra-sk/modules/clipboard-sk/clipboard-sk_puppeteer_test.ts
new file mode 100644
index 0000000..8357362
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk_puppeteer_test.ts
@@ -0,0 +1,27 @@
+import { expect } from 'chai';
+import {
+ inBazel, loadCachedTestBed, takeScreenshot, TestBed,
+} from '../../../puppeteer-tests/util';
+
+describe('clipboard-sk', () => {
+ let testBed: TestBed;
+ before(async () => {
+ testBed = await loadCachedTestBed();
+ });
+
+ beforeEach(async () => {
+ // Remove the /dist/ below for //infra-sk elements.
+ await testBed.page.goto(inBazel() ? testBed.baseUrl : `${testBed.baseUrl}/clipboard-sk.html`);
+ await testBed.page.setViewport({ width: 400, height: 550 });
+ });
+
+ it('should render the demo page (smoke test)', async () => {
+ expect(await testBed.page.$$('clipboard-sk')).to.have.length(2);
+ });
+
+ describe('screenshots', () => {
+ it('shows the default view', async () => {
+ await takeScreenshot(testBed.page, 'infra-sk', 'clipboard-sk');
+ });
+ });
+});
diff --git a/infra-sk/modules/clipboard-sk/clipboard-sk_test.ts b/infra-sk/modules/clipboard-sk/clipboard-sk_test.ts
new file mode 100644
index 0000000..981edb7
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/clipboard-sk_test.ts
@@ -0,0 +1,26 @@
+import './index';
+import { assert } from 'chai';
+import { $$ } from 'common-sk/modules/dom';
+import { ClipboardSk, defaultToolTipMessage } from './clipboard-sk';
+
+import { setUpElementUnderTest } from '../test_util';
+import { TooltipSk } from '../tooltip-sk/tooltip-sk';
+
+const testMessage = 'This should end up in the clipboard';
+
+describe('clipboard-sk', () => {
+ const newInstance = setUpElementUnderTest<ClipboardSk>('clipboard-sk');
+
+ let element: ClipboardSk;
+ beforeEach(() => {
+ element = newInstance((el: ClipboardSk) => {
+ el.value = testMessage;
+ });
+ });
+
+ describe('on construction', () => {
+ it('has the right tooltip value', () => {
+ assert.equal($$<TooltipSk>('tooltip-sk', element)!.value, defaultToolTipMessage);
+ });
+ });
+});
diff --git a/infra-sk/modules/clipboard-sk/index.ts b/infra-sk/modules/clipboard-sk/index.ts
new file mode 100644
index 0000000..02e966e
--- /dev/null
+++ b/infra-sk/modules/clipboard-sk/index.ts
@@ -0,0 +1 @@
+import './clipboard-sk';
diff --git a/infra-sk/modules/tooltip-sk/tooltip-sk.ts b/infra-sk/modules/tooltip-sk/tooltip-sk.ts
index c0ee197..3fe586b 100644
--- a/infra-sk/modules/tooltip-sk/tooltip-sk.ts
+++ b/infra-sk/modules/tooltip-sk/tooltip-sk.ts
@@ -36,6 +36,8 @@
connectedCallback(): void {
super.connectedCallback();
+ this._upgradeProperty('value');
+ this._upgradeProperty('target');
this._render();
this.hide();
@@ -51,7 +53,7 @@
// back to this element. We require an id for this to work, so assign a
// random id if one hasn't been set.
if (!this.id) {
- this.id = `x${Math.random()}`;
+ this.id = `x${`${Math.random()}`.slice(2)}`;
}
this.connectToTarget();