mirror of
https://github.com/discourse/discourse.git
synced 2025-02-09 21:04:48 +00:00
This change adds a progress bar to toast notifications when autoClose is enabled (true by default). The progress bar allows users to visually see how long is left before the notification disappears. When hovered on desktop, the progress and autoclose timer will be paused, it will resume again once the mouse is moved away from the toast notification.
136 lines
3.3 KiB
Plaintext
136 lines
3.3 KiB
Plaintext
import Component from "@glimmer/component";
|
|
import { tracked } from "@glimmer/tracking";
|
|
import { registerDestructor } from "@ember/destroyable";
|
|
import { action } from "@ember/object";
|
|
import { cancel } from "@ember/runloop";
|
|
import Modifier from "ember-modifier";
|
|
import concatClass from "discourse/helpers/concat-class";
|
|
import discourseLater from "discourse-common/lib/later";
|
|
import { bind } from "discourse-common/utils/decorators";
|
|
|
|
const CSS_TRANSITION_DELAY_MS = 300;
|
|
const TRANSITION_CLASS = "-fade-out";
|
|
|
|
class AutoCloseToast extends Modifier {
|
|
element;
|
|
close;
|
|
duration;
|
|
transitionLaterHandler;
|
|
closeLaterHandler;
|
|
progressBar;
|
|
progressAnimation;
|
|
|
|
constructor(owner, args) {
|
|
super(owner, args);
|
|
|
|
registerDestructor(this, (instance) => instance.cleanup());
|
|
}
|
|
|
|
modify(element, _, { close, duration, progressBar }) {
|
|
this.element = element;
|
|
this.close = close;
|
|
this.duration = duration;
|
|
this.timeRemaining = duration;
|
|
this.progressBar = progressBar;
|
|
this.element.addEventListener("mouseenter", this.stopTimer, {
|
|
passive: true,
|
|
});
|
|
this.element.addEventListener("mouseleave", this.startTimer, {
|
|
passive: true,
|
|
});
|
|
this.startTimer();
|
|
}
|
|
|
|
@bind
|
|
startTimer() {
|
|
this.startProgressAnimation();
|
|
|
|
this.transitionLaterHandler = discourseLater(() => {
|
|
this.element.classList.add(TRANSITION_CLASS);
|
|
|
|
this.closeLaterHandler = discourseLater(() => {
|
|
this.close();
|
|
}, CSS_TRANSITION_DELAY_MS);
|
|
}, this.timeRemaining);
|
|
}
|
|
|
|
@bind
|
|
stopTimer() {
|
|
this.pauseProgressAnimation();
|
|
cancel(this.transitionLaterHandler);
|
|
cancel(this.closeLaterHandler);
|
|
}
|
|
|
|
@bind
|
|
startProgressAnimation() {
|
|
if (!this.progressBar) {
|
|
return;
|
|
}
|
|
|
|
if (this.progressAnimation) {
|
|
this.progressAnimation.play();
|
|
this.progressBar.style.opacity = 1;
|
|
return;
|
|
}
|
|
|
|
this.progressAnimation = this.progressBar.animate(
|
|
{ transform: `scaleX(0)` },
|
|
{ duration: this.duration, fill: "forwards" }
|
|
);
|
|
}
|
|
|
|
@bind
|
|
pauseProgressAnimation() {
|
|
if (
|
|
!this.progressAnimation ||
|
|
this.progressAnimation.currentTime === this.duration
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.progressAnimation.pause();
|
|
this.progressBar.style.opacity = 0.5;
|
|
this.timeRemaining = this.duration - this.progressAnimation.currentTime;
|
|
}
|
|
|
|
cleanup() {
|
|
this.stopTimer();
|
|
this.element.removeEventListener("mouseenter", this.stopTimer);
|
|
this.element.removeEventListener("mouseleave", this.startTimer);
|
|
this.progressBar = null;
|
|
}
|
|
}
|
|
|
|
export default class DToast extends Component {
|
|
@tracked progressBar;
|
|
|
|
@action
|
|
registerProgressBar(element) {
|
|
this.progressBar = element;
|
|
}
|
|
|
|
<template>
|
|
<output
|
|
role={{if @toast.options.autoClose "status" "log"}}
|
|
key={{@toast.id}}
|
|
class={{concatClass "fk-d-toast" @toast.options.class}}
|
|
{{(if
|
|
@toast.options.autoClose
|
|
(modifier
|
|
AutoCloseToast
|
|
close=@toast.close
|
|
duration=@toast.options.duration
|
|
progressBar=this.progressBar
|
|
)
|
|
)}}
|
|
>
|
|
<@toast.options.component
|
|
@data={{@toast.options.data}}
|
|
@close={{@toast.close}}
|
|
@autoClose={{@toast.options.autoClose}}
|
|
@onRegisterProgressBar={{this.registerProgressBar}}
|
|
/>
|
|
</output>
|
|
</template>
|
|
}
|