From e68c534e6c27436d24a9ae51c210c4fa2b1b37ae Mon Sep 17 00:00:00 2001 From: Jason <84899178+jasonhenriquez@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:19:11 +0000 Subject: [PATCH] A new way to subscribe (#4238) * Implement styling for new subscribe button * Implement dropdown element and styling * Implements remaining styling and logic * Correct use of aria-selected in FT Note: was able to add aria-controls to ft-profile-selector because it keeps the hidden dropdown in the DOM. The same is not true of the ft-icon-button or ft-subscribe-button. Main point: aria-expanded should go on the button opening the dropdown, not the dropdown itself. * Resolve subscribe button staying upon bug * Remove unneeded vendor-specific transition prefixes See discussion here: https://www.web-plus-plus.com/Articles/css-transition-moz-and-webkit-vs-css3 --- .../ft-channel-bubble/ft-channel-bubble.css | 5 - .../ft-icon-button/ft-icon-button.vue | 3 +- src/renderer/components/ft-input/ft-input.css | 8 - .../ft-profile-bubble/ft-profile-bubble.css | 5 - .../ft-profile-selector.css | 5 - .../ft-profile-selector.js | 4 + .../ft-profile-selector.vue | 5 +- .../ft-subscribe-button.css | 8 - .../ft-subscribe-button.js | 89 +++++++++-- .../ft-subscribe-button.scss | 141 ++++++++++++++++++ .../ft-subscribe-button.vue | 87 ++++++++++- .../side-nav-more-options.css | 4 - src/renderer/components/side-nav/side-nav.css | 4 - src/renderer/main.js | 2 + src/renderer/views/Channel/Channel.css | 1 + .../SubscribedChannels/SubscribedChannels.vue | 3 +- static/locales/en-US.yaml | 2 + 17 files changed, 308 insertions(+), 68 deletions(-) delete mode 100644 src/renderer/components/ft-subscribe-button/ft-subscribe-button.css create mode 100644 src/renderer/components/ft-subscribe-button/ft-subscribe-button.scss diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css index fcb7b96e5..1a4878d64 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css @@ -11,17 +11,12 @@ overflow: hidden; color: inherit; text-decoration: none; - -webkit-transition: background 0.2s ease-out; - -moz-transition: background 0.2s ease-out; - -o-transition: background 0.2s ease-out; transition: background 0.2s ease-out; } .bubblePadding:hover { background-color: var(--side-nav-hover-color); color: var(--side-nav-hover-text-color); - -moz-transition: background 0.2s ease-in; - -o-transition: background 0.2s ease-in; transition: background 0.2s ease-in; } diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.vue b/src/renderer/components/ft-icon-button/ft-icon-button.vue index b1760e528..c699f4392 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.vue +++ b/src/renderer/components/ft-icon-button/ft-icon-button.vue @@ -14,6 +14,7 @@ }" tabindex="0" role="button" + :aria-expanded="dropdownShown" @click="handleIconClick" @mousedown="handleIconMouseDown" @keydown.enter.prevent="handleIconClick" @@ -33,7 +34,6 @@ v-if="dropdownOptions.length > 0" class="list" role="listbox" - :aria-expanded="dropdownShown" >
  • { + return profile?.name?.length > 0 ? Array.from(profile.name)[0].toUpperCase() : '' + }) + }, + profileList: function () { return this.$store.getters.getProfileList }, + /* sort by 'All Channels' -> active profile -> unsubscribed channels -> subscribed channels */ + profileDisplayList: function () { + const mainProfileAndActiveProfile = [this.profileList[0]] + if (this.activeProfile._id !== MAIN_PROFILE_ID) { + mainProfileAndActiveProfile.push(this.activeProfile) + } + + return [ + ...mainProfileAndActiveProfile, + ...this.profileList.filter((profile, i) => + i !== 0 && !this.isActiveProfile(profile) && !this.isProfileSubscribed(profile)), + ...this.profileList.filter((profile, i) => + i !== 0 && !this.isActiveProfile(profile) && this.isProfileSubscribed(profile)) + ] + }, + activeProfile: function () { return this.$store.getters.getActiveProfile }, subscriptionInfo: function () { - return this.activeProfile.subscriptions.find((channel) => { - return channel.id === this.channelId - }) ?? null - }, - - isSubscribed: function () { - return this.subscriptionInfo !== null + return this.subscriptionInfoForProfile(this.activeProfile) }, hideChannelSubscriptions: function () { @@ -55,23 +80,27 @@ export default defineComponent({ }, subscribedText: function () { - let subscribedValue = (this.isSubscribed ? this.$t('Channel.Unsubscribe') : this.$t('Channel.Subscribe')).toUpperCase() + let subscribedValue = (this.isProfileSubscribed(this.activeProfile) ? this.$t('Channel.Unsubscribe') : this.$t('Channel.Subscribe')).toUpperCase() if (this.subscriptionCountText !== '' && !this.hideChannelSubscriptions) { subscribedValue += ' ' + this.subscriptionCountText } return subscribedValue + }, + + isProfileDropdownEnabled: function () { + return !this.hideProfileDropdownToggle && this.profileList.length > 1 } }, methods: { - handleSubscription: function () { + handleSubscription: function (profile = this.activeProfile) { if (this.channelId === '') { return } - const currentProfile = deepCopy(this.activeProfile) + const currentProfile = deepCopy(profile) - if (this.isSubscribed) { + if (this.isProfileSubscribed(profile)) { currentProfile.subscriptions = currentProfile.subscriptions.filter((channel) => { return channel.id !== this.channelId }) @@ -79,16 +108,16 @@ export default defineComponent({ this.updateProfile(currentProfile) showToast(this.$t('Channel.Channel has been removed from your subscriptions')) - if (this.activeProfile._id === MAIN_PROFILE_ID) { + if (profile._id === MAIN_PROFILE_ID) { // Check if a subscription exists in a different profile. // Remove from there as well. let duplicateSubscriptions = 0 - this.profileList.forEach((profile) => { - if (profile._id === MAIN_PROFILE_ID) { + this.profileList.forEach((profileInList) => { + if (profileInList._id === MAIN_PROFILE_ID) { return } - duplicateSubscriptions += this.unsubscribe(profile, this.channelId) + duplicateSubscriptions += this.unsubscribe(profileInList, this.channelId) }) if (duplicateSubscriptions > 0) { @@ -107,7 +136,7 @@ export default defineComponent({ this.updateProfile(currentProfile) showToast(this.$t('Channel.Added channel to your subscriptions')) - if (this.activeProfile._id !== MAIN_PROFILE_ID) { + if (profile._id !== MAIN_PROFILE_ID) { const primaryProfile = deepCopy(this.profileList.find(prof => { return prof._id === MAIN_PROFILE_ID })) @@ -122,6 +151,34 @@ export default defineComponent({ } } } + + if (this.isProfileDropdownEnabled && !this.isProfileDropdownOpen) { + this.toggleProfileDropdown() + } + }, + + handleProfileDropdownFocusOut: function () { + if (!this.$refs.subscribeButton.matches(':focus-within')) { + this.isProfileDropdownOpen = false + } + }, + + toggleProfileDropdown: function() { + this.isProfileDropdownOpen = !this.isProfileDropdownOpen + }, + + isActiveProfile: function (profile) { + return profile._id === this.activeProfile._id + }, + + subscriptionInfoForProfile: function (profile) { + return profile.subscriptions.find((channel) => { + return channel.id === this.channelId + }) ?? null + }, + + isProfileSubscribed: function (profile) { + return this.subscriptionInfoForProfile(profile) !== null }, unsubscribe: function(profile, channelId) { diff --git a/src/renderer/components/ft-subscribe-button/ft-subscribe-button.scss b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.scss new file mode 100644 index 000000000..1d8667fa1 --- /dev/null +++ b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.scss @@ -0,0 +1,141 @@ +.buttonList { + margin: 5px; + margin-block-end: 10px; + border-radius: 4px; + block-size: fit-content; + box-shadow: 0px 1px 2px rgb(0 0 0 / 50%); + display: flex; + flex-wrap: nowrap; + /* addresses odd clipping behavior when adjusting window size */ + background-color: var(--primary-color); +} + +.ftSubscribeButton { + position: relative; + text-align: start; +} + +/* Ensures style here overrides style of .btn */ +.subscribeButton.btn { + min-inline-size: 150px; + white-space: initial; +} + +.subscribeButton.btn, .profileDropdownToggle.btn { + align-self: center; + margin-block: 0; + margin-inline: 0; +} + +.dropdownOpened { + .subscribeButton, .profileDropdownToggle { + border-end-start-radius: 0; + border-end-end-radius: 0; + } +} + +.profileDropdownToggle.btn { + border-inline-start: none !important; + border-start-start-radius: 0; + border-end-start-radius: 0; + display: inline-block; + min-inline-size: 1em; + padding-inline: 10px; + box-sizing: content-box; +} + +.hasProfileDropdownToggle { + .subscribeButton.btn { + min-inline-size: 100px; + padding-inline: 5px; + border-inline-end: 2px solid var(--primary-color-active) !important; + border-start-end-radius: 0; + border-end-end-radius: 0; + box-sizing: content-box; + } +} + +.hasProfileDropdownToggle > .subscribeButton.btn, .profileDropdownToggle.btn { + padding-block: 5px; + padding-inline: 6px; + box-shadow: none; + flex: auto; + block-size: 2em; +} + +.profileDropdown { + background-color: var(--side-nav-color); + box-shadow: 0 1px 2px rgb(0 0 0 / 50%); + color: var(--secondary-text-color); + display: inline; + font-size: 12px; + max-block-size: 200px; + margin-block: -10px 0; + margin-inline: 5px 0; + overflow-y: scroll; + position: absolute; + text-align: center; + -webkit-user-select: none; + user-select: none; + z-index: 3; + // accounts for parent's left and right margins + inline-size: calc(100% - 10px); + + .profileList { + list-style-type: none; + margin: 0; + padding-inline: 0; + } + + .profile { + cursor: pointer; + display: flex; + gap: 0.5em; + padding-inline-start: 0.5em; + block-size: 50px; + align-items: center; + transition: background 0.2s ease-out; + + &:hover { + background-color: var(--side-nav-hover-color); + color: var(--side-nav-hover-text-color); + transition: background 0.2s ease-in; + } + + .colorOption { + inline-size: 40px; + block-size: 40px; + cursor: pointer; + align-items: center; + display: flex; + justify-content: center; + flex-shrink: 0; + border-radius: 50%; + -webkit-border-radius: 50%; + } + + .initial { + font-size: 20px; + line-height: 1em; + text-align: center; + user-select: none; + -webkit-user-select: none; + } + + .profileName { + padding-inline-end: 1em; + text-align: start; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &.subscribed { + background-color: var(--primary-color); + + .profileName { + color: var(--text-with-main-color); + } + } + } +} diff --git a/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue index 11bf54e7e..7aded099d 100644 --- a/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue +++ b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue @@ -1,12 +1,83 @@