From 0f1479e8962675530f9a8a332a0c90c7d63b68e6 Mon Sep 17 00:00:00 2001 From: Jordan Vidrine <30537603+jordanvidrine@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:02:26 -0500 Subject: [PATCH] UX: Refactor AI summarizing animation (#22839) --- .../app/components/ai-summary-skeleton.hbs | 26 +++ .../app/components/ai-summary-skeleton.js | 92 +++++++++ .../discourse/app/widgets/summary-box.js | 43 +---- .../common/base/topic-summary.scss | 179 +++++++++++++++--- 4 files changed, 278 insertions(+), 62 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/ai-summary-skeleton.hbs create mode 100644 app/assets/javascripts/discourse/app/components/ai-summary-skeleton.js diff --git a/app/assets/javascripts/discourse/app/components/ai-summary-skeleton.hbs b/app/assets/javascripts/discourse/app/components/ai-summary-skeleton.hbs new file mode 100644 index 00000000000..410f6376e0b --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/ai-summary-skeleton.hbs @@ -0,0 +1,26 @@ + + + +
+ {{i18n "summary.in_progress"}} +
+ + . + . + . + +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/ai-summary-skeleton.js b/app/assets/javascripts/discourse/app/components/ai-summary-skeleton.js new file mode 100644 index 00000000000..1323a7b3bf9 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/ai-summary-skeleton.js @@ -0,0 +1,92 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { tracked } from "@glimmer/tracking"; +import discourseLater from "discourse-common/lib/later"; +import { cancel } from "@ember/runloop"; + +class Block { + @tracked show = false; + @tracked shown = false; + @tracked blinking = false; + + constructor(args = {}) { + this.show = args.show ?? false; + this.shown = args.shown ?? false; + } +} + +const BLOCKS_SIZE = 20; // changing this requires to change css accordingly + +export default class AiSummarySkeleton extends Component { + blocks = [...Array.from({ length: BLOCKS_SIZE }, () => new Block())]; + + #nextBlockBlinkingTimer; + #blockBlinkingTimer; + #blockShownTimer; + + @action + setupAnimation() { + this.blocks.firstObject.show = true; + this.blocks.firstObject.shown = true; + } + + @action + onBlinking(block) { + if (!block.blinking) { + return; + } + + block.show = false; + + this.#nextBlockBlinkingTimer = discourseLater( + this, + () => { + this.#nextBlock(block).blinking = true; + }, + 250 + ); + + this.#blockBlinkingTimer = discourseLater( + this, + () => { + block.blinking = false; + }, + 500 + ); + } + + @action + onShowing(block) { + if (!block.show) { + return; + } + + this.#blockShownTimer = discourseLater( + this, + () => { + this.#nextBlock(block).show = true; + this.#nextBlock(block).shown = true; + + if (this.blocks.lastObject === block) { + this.blocks.firstObject.blinking = true; + } + }, + 250 + ); + } + + @action + teardownAnimation() { + cancel(this.#blockShownTimer); + cancel(this.#nextBlockBlinkingTimer); + cancel(this.#blockBlinkingTimer); + } + + #nextBlock(currentBlock) { + if (currentBlock === this.blocks.lastObject) { + return this.blocks.firstObject; + } else { + return this.blocks.objectAt(this.blocks.indexOf(currentBlock) + 1); + } + } +} diff --git a/app/assets/javascripts/discourse/app/widgets/summary-box.js b/app/assets/javascripts/discourse/app/widgets/summary-box.js index 01f3b8c5774..fa0e425a1b6 100644 --- a/app/assets/javascripts/discourse/app/widgets/summary-box.js +++ b/app/assets/javascripts/discourse/app/widgets/summary-box.js @@ -10,39 +10,6 @@ import { h } from "virtual-dom"; import { iconNode } from "discourse-common/lib/icon-library"; import RenderGlimmer from "discourse/widgets/render-glimmer"; -createWidget("summary-skeleton", { - tagName: "section.placeholder-summary", - - html() { - const html = []; - - html.push(this.buildPlaceholderDiv()); - html.push(this.buildPlaceholderDiv()); - html.push(this.buildPlaceholderDiv()); - - html.push( - h("span", {}, [ - h( - "div.placeholder-generating-summary-text", - {}, - I18n.t("summary.in_progress") - ), - h("span.ai-summarizing-indicator__wave", {}, [ - h("span.ai-summarizing-indicator__dot", "."), - h("span.ai-summarizing-indicator__dot", "."), - h("span.ai-summarizing-indicator__dot", "."), - ]), - ]) - ); - - return html; - }, - - buildPlaceholderDiv() { - return h("div.placeholder-summary-text.placeholder-animation"); - }, -}); - export default createWidget("summary-box", { tagName: "article.summary-box", buildKey: (attrs) => `summary-box-${attrs.topicId}`, @@ -76,13 +43,21 @@ export default createWidget("summary-box", { html.push(h("div.summarized-on", {}, summarizationInfo)); } else { - html.push(this.attach("summary-skeleton")); + html.push(this.buildSummarySkeleton()); this.fetchSummary(attrs.topicId, attrs.skipAgeCheck); } return html; }, + buildSummarySkeleton() { + return new RenderGlimmer( + this, + "div.ai-summary__container", + hbs`{{ai-summary-skeleton}}` + ); + }, + buildTooltip(attrs) { return new RenderGlimmer( this, diff --git a/app/assets/stylesheets/common/base/topic-summary.scss b/app/assets/stylesheets/common/base/topic-summary.scss index 4c9d1dbcfeb..781f8ddc37b 100644 --- a/app/assets/stylesheets/common/base/topic-summary.scss +++ b/app/assets/stylesheets/common/base/topic-summary.scss @@ -1,33 +1,124 @@ -.topic-map { - .toggle-summary { - .summarization-buttons { +.topic-map .toggle-summary { + .summarization-buttons { + display: flex; + } + + .ai-summary { + &__list { + list-style: none; display: flex; + flex-wrap: wrap; + padding: 0; + margin: 0; } + &__list-item { + background: var(--primary-300); + border-radius: var(--d-border-radius); + margin-right: 8px; + margin-bottom: 8px; + height: 18px; + opacity: 0; + display: block; + &:nth-child(1) { + width: 10%; + } - .placeholder-summary { - padding-top: 0.5em; + &:nth-child(2) { + width: 12%; + } + + &:nth-child(3) { + width: 18%; + } + + &:nth-child(4) { + width: 14%; + } + + &:nth-child(5) { + width: 18%; + } + + &:nth-child(6) { + width: 14%; + } + + &:nth-child(7) { + width: 22%; + } + + &:nth-child(8) { + width: 05%; + } + + &:nth-child(9) { + width: 25%; + } + + &:nth-child(10) { + width: 14%; + } + + &:nth-child(11) { + width: 18%; + } + + &:nth-child(12) { + width: 12%; + } + + &:nth-child(13) { + width: 22%; + } + + &:nth-child(14) { + width: 18%; + } + + &:nth-child(15) { + width: 13%; + } + + &:nth-child(16) { + width: 22%; + } + + &:nth-child(17) { + width: 19%; + } + + &:nth-child(18) { + width: 13%; + } + + &:nth-child(19) { + width: 22%; + } + + &:nth-child(20) { + width: 25%; + } + &.is-shown { + opacity: 1; + } + &.show { + animation: appear 0.5s cubic-bezier(0.445, 0.05, 0.55, 0.95) 0s forwards; + } + &.blink { + animation: blink 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both; + } } - - .placeholder-summary-text { - display: inline-block; - height: 1em; - margin-top: 0.6em; - width: 100%; - } - - .placeholder-generating-summary-text { + &__generating-text { display: inline-block; margin-left: 3px; } - - .ai-summarizing-indicator__wave { + &__indicator-wave { flex: 0 0 auto; display: inline-flex; } - - .ai-summarizing-indicator__dot { + &__indicator-dot { display: inline-block; - animation: ai-summarizing-indicator__wave 1.8s linear infinite; + animation: ai-summary__indicator-wave 1.8s linear infinite; &:nth-child(2) { animation-delay: -1.6s; } @@ -35,22 +126,33 @@ animation-delay: -1.4s; } } + } - .summarized-on { - text-align: right; + .placeholder-summary { + padding-top: 0.5em; + } - .info-icon { - margin-left: 3px; - } + .placeholder-summary-text { + display: inline-block; + height: 1em; + margin-top: 0.6em; + width: 100%; + } + + .summarized-on { + text-align: right; + + .info-icon { + margin-left: 3px; } + } - .outdated-summary { - color: var(--primary-medium); - } + .outdated-summary { + color: var(--primary-medium); } } -@keyframes ai-summarizing-indicator__wave { +@keyframes ai-summary__indicator-wave { 0%, 60%, 100% { @@ -60,3 +162,24 @@ transform: translateY(-0.2em); } } + +@keyframes appear { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes blink { + 0% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 1; + } +}