DEV: Make indicator wave a reusable component (#807)
Previously we had some hardcoded markup with scss making a loading indicator wave. This code was being duplicated and used in both semantic search and summarization. We want to add the indicator wave to the AI helper diff modal as well and have the text flashing instead of the loading spinner. To ensure we do not repeat ourselves, in this PR we turn the summary indicator wave into a reusable template only component called: `AiIndicatorWave`. We then apply the usage of that component to semantic search, summarization, and the composer helper modal.
This commit is contained in:
parent
1e155942bb
commit
e666266473
|
@ -0,0 +1,12 @@
|
|||
const indicatorDots = [".", ".", "."];
|
||||
const AiIndicatorWave = <template>
|
||||
{{#if @loading}}
|
||||
<span class="ai-indicator-wave">
|
||||
{{#each indicatorDots as |dot|}}
|
||||
<span class="ai-indicator-wave__dot">{{dot}}</span>
|
||||
{{/each}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</template>;
|
||||
|
||||
export default AiIndicatorWave;
|
|
@ -9,6 +9,7 @@ import { cancel } from "@ember/runloop";
|
|||
import concatClass from "discourse/helpers/concat-class";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import AiIndicatorWave from "./ai-indicator-wave";
|
||||
|
||||
class Block {
|
||||
@tracked show = false;
|
||||
|
@ -118,11 +119,7 @@ export default class AiSummarySkeleton extends Component {
|
|||
<div class="ai-summary__generating-text">
|
||||
{{i18n "summary.in_progress"}}
|
||||
</div>
|
||||
<span class="ai-summary__indicator-wave">
|
||||
<span class="ai-summary__indicator-dot">.</span>
|
||||
<span class="ai-summary__indicator-dot">.</span>
|
||||
<span class="ai-summary__indicator-dot">.</span>
|
||||
</span>
|
||||
<AiIndicatorWave @loading={{true}} />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -3,13 +3,13 @@ import { tracked } from "@glimmer/tracking";
|
|||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
||||
import CookText from "discourse/components/cook-text";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DModal from "discourse/components/d-modal";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import AiIndicatorWave from "../ai-indicator-wave";
|
||||
|
||||
export default class ModalDiffModal extends Component {
|
||||
@service currentUser;
|
||||
|
@ -65,25 +65,23 @@ export default class ModalDiffModal extends Component {
|
|||
@closeModal={{@closeModal}}
|
||||
>
|
||||
<:body>
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||
{{#if this.loading}}
|
||||
<div class="composer-ai-helper-modal__loading">
|
||||
<CookText @rawText={{this.selectedText}} />
|
||||
</div>
|
||||
{{#if this.loading}}
|
||||
<div class="composer-ai-helper-modal__loading">
|
||||
<CookText @rawText={{@model.selectedText}} />
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if this.diff}}
|
||||
{{htmlSafe this.diff}}
|
||||
{{else}}
|
||||
{{#if this.diff}}
|
||||
{{htmlSafe this.diff}}
|
||||
{{else}}
|
||||
<div class="composer-ai-helper-modal__old-value">
|
||||
{{@model.selectedText}}
|
||||
</div>
|
||||
<div class="composer-ai-helper-modal__old-value">
|
||||
{{@model.selectedText}}
|
||||
</div>
|
||||
|
||||
<div class="composer-ai-helper-modal__new-value">
|
||||
{{this.suggestion}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="composer-ai-helper-modal__new-value">
|
||||
{{this.suggestion}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</ConditionalLoadingSpinner>
|
||||
{{/if}}
|
||||
|
||||
</:body>
|
||||
|
||||
|
@ -93,7 +91,9 @@ export default class ModalDiffModal extends Component {
|
|||
class="btn-primary"
|
||||
@label="discourse_ai.ai_helper.context_menu.loading"
|
||||
@disabled={{true}}
|
||||
/>
|
||||
>
|
||||
<AiIndicatorWave @loading={{this.loading}} />
|
||||
</DButton>
|
||||
{{else}}
|
||||
<DButton
|
||||
class="btn-primary confirm"
|
||||
|
|
|
@ -11,6 +11,7 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
|||
import { isValidSearchTerm, translateResults } from "discourse/lib/search";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
import I18n from "I18n";
|
||||
import AiIndicatorWave from "../../components/ai-indicator-wave";
|
||||
|
||||
export default class SemanticSearch extends Component {
|
||||
static shouldRender(_args, { siteSettings }) {
|
||||
|
@ -173,13 +174,7 @@ export default class SemanticSearch extends Component {
|
|||
{{this.searchStateText}}
|
||||
</div>
|
||||
|
||||
{{#if this.searching}}
|
||||
<span class="semantic-search__indicator-wave">
|
||||
<span class="semantic-search__indicator-dot">.</span>
|
||||
<span class="semantic-search__indicator-dot">.</span>
|
||||
<span class="semantic-search__indicator-dot">.</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
<AiIndicatorWave @loading={{this.searching}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,3 +29,32 @@ article.streaming .cooked {
|
|||
animation: flashing 1.5s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ai-indicator-wave {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: initial;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-0.2em);
|
||||
}
|
||||
}
|
||||
|
||||
.ai-indicator-wave {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
|
||||
&__dot {
|
||||
display: inline-block;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: ai-indicator-wave 1.8s linear infinite;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: -1.6s;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: -1.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
|
||||
.ai-indicator-wave {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
||||
.semantic-search {
|
||||
&__searching {
|
||||
display: flex;
|
||||
|
@ -23,22 +27,6 @@
|
|||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
&__indicator-wave {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
&__indicator-dot {
|
||||
display: inline-block;
|
||||
animation: ai-summary__indicator-wave 1.8s linear infinite;
|
||||
&:nth-child(2) {
|
||||
animation-delay: -1.6s;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: -1.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semantic-search__entries {
|
||||
|
|
|
@ -157,22 +157,6 @@
|
|||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
}
|
||||
&__indicator-wave {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
}
|
||||
&__indicator-dot {
|
||||
display: inline-block;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: ai-summary__indicator-wave 1.8s linear infinite;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: -1.6s;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: -1.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-summary {
|
||||
|
@ -211,17 +195,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes ai-summary__indicator-wave {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: initial;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-0.2em);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
|
|
@ -294,7 +294,7 @@ en:
|
|||
missing_content: "Please enter some content to generate suggestions."
|
||||
context_menu:
|
||||
trigger: "Ask AI"
|
||||
loading: "AI is generating..."
|
||||
loading: "AI is generating"
|
||||
cancel: "Cancel"
|
||||
regen: "Try Again"
|
||||
confirm: "Confirm"
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { render } from "@ember/test-helpers";
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import AiIndicatorWave from "discourse/plugins/discourse-ai/discourse/components/ai-indicator-wave";
|
||||
|
||||
module("Integration | Component | ai-indicator-wave", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("it renders an indicator wave", async function (assert) {
|
||||
await render(<template><AiIndicatorWave @loading={{true}} /></template>);
|
||||
assert.dom(".ai-indicator-wave").exists();
|
||||
});
|
||||
|
||||
test("it does not render the indicator wave when loading is false", async function (assert) {
|
||||
await render(<template><AiIndicatorWave @loading={{false}} /></template>);
|
||||
assert.dom(".ai-indicator-wave").doesNotExist();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue