From ae27beb01a37975f600b3d65aafecea6a790f150 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Thu, 14 Sep 2023 18:48:29 +0200 Subject: [PATCH] UI: improves remove channel animation (#23585) Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com> --- .../discourse/components/chat-channel-row.gjs | 216 ++++++++---------- .../stylesheets/common/chat-channel-row.scss | 5 + .../stylesheets/mobile/chat-channel-row.scss | 83 ++++--- 3 files changed, 152 insertions(+), 152 deletions(-) diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs index 0c4b5322781..98eace72938 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.gjs @@ -15,11 +15,10 @@ import I18n from "I18n"; import { modifier } from "ember-modifier"; import { bind } from "discourse-common/utils/decorators"; import { tracked } from "@glimmer/tracking"; -import discourseLater from "discourse-common/lib/later"; -import { cancel } from "@ember/runloop"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import icon from "discourse-common/helpers/d-icon"; +import { htmlSafe } from "@ember/template"; -const RESET_CLASS = "-reset"; const FADEOUT_CLASS = "-fade-out"; export default class ChatChannelRow extends Component { @@ -40,94 +39,109 @@ export default class ChatChannelRow extends Component { data-chat-channel-id={{@channel.id}} {{didInsert this.startTrackingStatus}} {{willDestroy this.stopTrackingStatus}} - {{(if this.shouldHandleSwipe (modifier this.registerSwipableRow))}} - {{(if this.shouldHandleSwipe (modifier this.handleSwipe))}} {{(if this.shouldRemoveChannel (modifier this.onRemoveChannel))}} - {{(if this.shouldResetRow (modifier this.onResetRow))}} > - - +
+ + - {{#if - (and @options.leaveButton @channel.isFollowing this.site.desktopView) - }} - - {{/if}} + {{#if + (and @options.leaveButton @channel.isFollowing this.site.desktopView) + }} + + {{/if}} +
- {{#if this.shouldHandleSwipe}} + {{#if this.showRemoveButton}}
- {{#if this.isCancelling}} - {{this.cancelActionLabel}} - {{else}} - {{this.removeActionLabel}} - {{/if}} + {{icon "times-circle"}}
{{/if}} - @service router; - @service chat; - @service capabilities; - @service currentUser; - @service site; @service api; + @service capabilities; + @service chat; + @service currentUser; + @service router; + @service site; + @tracked isAtThreshold = false; @tracked shouldRemoveChannel = false; - @tracked hasReachedThreshold = false; - @tracked isCancelling = false; - @tracked shouldResetRow = false; - @tracked actionButton; - @tracked swipableRow; - - positionActionButton = modifier((element) => { - element.style.left = "100%"; - }); - - registerActionButton = modifier((element) => { - this.actionButton = element; - }); + @tracked showRemoveButton = false; + @tracked swipableRow = null; + @tracked shouldReset = false; + @tracked diff = 0; + @tracked rowStyle = null; + @tracked canSwipe = true; registerSwipableRow = modifier((element) => { this.swipableRow = element; }); - onRemoveChannel = modifier(() => { - this.swipableRow.classList.add(FADEOUT_CLASS); + onReset = modifier((element) => { + const handler = () => { + this.rowStyle = htmlSafe("margin-right: 0px;"); + this.showRemoveButton = false; + this.shouldReset = false; + }; - const handler = discourseLater(() => { - this.chat.unfollowChannel(this.args.channel).catch(popupAjaxError); - }, 250); + element.addEventListener("transitionend", handler, { once: true }); return () => { - cancel(handler); + element.removeEventListener("transitionend", handler); + this.rowStyle = htmlSafe("margin-right: 0px;"); + this.showRemoveButton = false; + this.shouldReset = false; }; }); + onRemoveChannel = modifier((element) => { + element.addEventListener( + "transitionend", + () => { + this.chat.unfollowChannel(this.args.channel).catch(popupAjaxError); + }, + { once: true } + ); + + element.classList.add(FADEOUT_CLASS); + }); + handleSwipe = modifier((element) => { element.addEventListener("touchstart", this.onSwipeStart, { passive: true, }); - element.addEventListener("touchmove", this.onSwipe); + element.addEventListener("touchmove", this.onSwipe, { + passive: true, + }); element.addEventListener("touchend", this.onSwipeEnd); return () => { @@ -137,93 +151,49 @@ export default class ChatChannelRow extends Component { }; }); - onResetRow = modifier(() => { - this.swipableRow.classList.add(RESET_CLASS); - this.swipableRow.style.left = "0px"; - - const handler = discourseLater(() => { - this.isCancelling = false; - this.hasReachedThreshold = false; - this.shouldResetRow = false; - this.swipableRow.classList.remove(RESET_CLASS); - }, 250); - - return () => { - cancel(handler); - this.swipableRow.classList.remove(RESET_CLASS); - }; - }); - - _lastX = null; - _towardsThreshold = false; - @bind onSwipeStart(event) { - this.hasReachedThreshold = false; - this.isCancelling = false; - this._lastX = this.initialX = event.changedTouches[0].screenX; + this._initialX = event.changedTouches[0].screenX; } @bind onSwipe(event) { - const touchX = event.changedTouches[0].screenX; - const diff = this.initialX - touchX; + this.showRemoveButton = true; + this.shouldReset = false; + this.isAtThreshold = false; - // we don't state to be too sensitive to the touch - if (Math.abs(this._lastX - touchX) > 5) { - this._towardsThreshold = this._lastX >= touchX; - this._lastX = touchX; - } + const threshold = window.innerWidth / 3; + const touchX = event.changedTouches[0].screenX; + + this.diff = this._initialX - touchX; + this.isAtThreshold = this.diff >= threshold; // ensures we will go back to the initial position when swiping very fast - if (diff < 10) { - this.isCancelling = false; - this.hasReachedThreshold = false; - this.swipableRow.style.left = "0px"; - return; - } + if (this.diff > 25) { + if (this.isAtThreshold) { + this.diff = threshold + (this.diff - threshold) * 0.1; + } - if (diff >= window.innerWidth / 3) { - this.isCancelling = false; - this.hasReachedThreshold = true; - return; + this.rowStyle = htmlSafe(`margin-right: ${this.diff}px;`); } else { - this.isCancelling = !this._towardsThreshold; - } - - if (diff > 25) { - this.actionButton.style.width = diff + "px"; - this.swipableRow.style.left = -(this.initialX - touchX) + "px"; + this.rowStyle = htmlSafe("margin-right: 0px;"); } } @bind - onSwipeEnd(event) { - this._lastX = null; - const diff = this.initialX - event.changedTouches[0].screenX; - - if (diff >= window.innerWidth / 3) { - this.swipableRow.style.left = "0px"; + onSwipeEnd() { + if (this.isAtThreshold) { + this.rowStyle = htmlSafe("margin-right: 0px;"); this.shouldRemoveChannel = true; - return; + } else { + this.shouldReset = true; } - - this.isCancelling = true; - this.shouldResetRow = true; } get shouldHandleSwipe() { return this.capabilities.touch && this.args.channel.isDirectMessageChannel; } - get cancelActionLabel() { - return I18n.t("cancel_value"); - } - - get removeActionLabel() { - return I18n.t("chat.remove"); - } - get leaveDirectMessageLabel() { return I18n.t("chat.direct_messages.leave"); } diff --git a/plugins/chat/assets/stylesheets/common/chat-channel-row.scss b/plugins/chat/assets/stylesheets/common/chat-channel-row.scss index cfacc351a5a..51cfbfa7b10 100644 --- a/plugins/chat/assets/stylesheets/common/chat-channel-row.scss +++ b/plugins/chat/assets/stylesheets/common/chat-channel-row.scss @@ -7,6 +7,11 @@ cursor: pointer; color: var(--primary-high); + &__content { + display: flex; + flex-grow: 1; + } + @media (hover: none) { &:hover, &:focus { diff --git a/plugins/chat/assets/stylesheets/mobile/chat-channel-row.scss b/plugins/chat/assets/stylesheets/mobile/chat-channel-row.scss index ba8c582816c..480365cdb0e 100644 --- a/plugins/chat/assets/stylesheets/mobile/chat-channel-row.scss +++ b/plugins/chat/assets/stylesheets/mobile/chat-channel-row.scss @@ -1,45 +1,70 @@ .chat-channel-row { - height: 4em; margin: 0; - padding: 0 1.5rem; border-radius: 0; border-bottom: 1px solid var(--primary-low); transition: height 0.25s ease-in-out, opacity 0.25s ease-out; transform-origin: top center; - will-change: height, left; - - &__action-btn { - display: flex; - align-items: center; - position: absolute; - top: 0px; - bottom: 0px; - padding-inline: 1rem; - - &.-cancel { - background: var(--primary-very-low); - color: var(--primary); - } - - &.-leave { - background: var(--danger); - color: var(--primary-very-low); - } - } - - &__action-btn-icon { - margin-left: 0.5rem; - } + will-change: height, opacity, left; + height: 4em; + position: relative; &.-fade-out { - background-color: var(--danger-low); + .chat-channel-row__content { + background-color: var(--danger-low); + } + height: 0 !important; overflow: hidden; opacity: 0.5 !important; } - &.-reset { - transition: left 0.25s ease-in-out; + &__content { + display: flex; + flex-grow: 1; + padding-inline: 1.5rem; + z-index: 2; + background: var(--primary-very-low); + box-sizing: border-box; + height: 100%; + transition: border-radius 0.25s ease-in-out; + + &.-animate-reset { + transition: margin-right 0.15s ease-out; + margin-right: 0px !important; + } + } + + &__action-btn { + z-index: 1; + display: flex; + align-items: center; + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + left: 0px; + background: var(--danger); + color: var(--primary-very-low); + + .d-icon { + transform-origin: 50% 50%; + transform-box: fill-box; + transition: scale 0.2s ease-out; + margin: 0 1rem 0 auto; + padding-left: 1rem; + } + + &.-not-at-threshold { + .d-icon { + scale: 0.7; + } + } + + &.-at-threshold { + .d-icon { + scale: 1.5; + } + } } .chat-channel-metadata {