Skip to content

Commit

Permalink
Migrate FtSubscribeButton to the composition API
Browse files Browse the repository at this point in the history
  • Loading branch information
absidue committed Dec 30, 2024
1 parent b01abad commit a777044
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 317 deletions.
2 changes: 1 addition & 1 deletion src/renderer/components/ChannelDetails/ChannelDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import FtCard from '../ft-card/ft-card.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtShareButton from '../ft-share-button/ft-share-button.vue'
import FtSubscribeButton from '../ft-subscribe-button/ft-subscribe-button.vue'
import FtSubscribeButton from '../FtSubscribeButton/FtSubscribeButton.vue'
import FtInput from '../ft-input/ft-input.vue'
import store from '../../store/index'
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/FtListChannel/FtListChannel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<script setup>
import { computed } from 'vue'
import FtSubscribeButton from '../ft-subscribe-button/ft-subscribe-button.vue'
import FtSubscribeButton from '../FtSubscribeButton/FtSubscribeButton.vue'
import store from '../../store/index'
Expand Down
314 changes: 314 additions & 0 deletions src/renderer/components/FtSubscribeButton/FtSubscribeButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
<template>
<div
ref="subscribeButton"
class="ftSubscribeButton"
:class="{ dropdownOpened: isProfileDropdownOpen}"
@focusout="handleProfileDropdownFocusOut"
>
<div
class="buttonList"
:class="{ hasProfileDropdownToggle: isProfileDropdownEnabled}"
>
<FtButton
:label="subscribedText"
:no-border="true"
class="subscribeButton"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="handleSubscription(activeProfile)"
/>
<FtPrompt
v-if="showUnsubscribePopupForProfile !== null"
:label="$t('Channels.Unsubscribe Prompt', { channelName: channelName })"
:option-names="[$t('Yes'), $t('No')]"
:option-values="['yes', 'no']"
:autosize="true"
@click="handleUnsubscribeConfirmation"
/>
<FtButton
v-if="isProfileDropdownEnabled"
:no-border="true"
:title="isProfileDropdownOpen ? $t('Profile.Close Profile Dropdown') : $t('Profile.Open Profile Dropdown')"
class="profileDropdownToggle"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
:aria-expanded="isProfileDropdownOpen"
@click="toggleProfileDropdown"
>
<FontAwesomeIcon
:icon="isProfileDropdownOpen ? ['fas', 'angle-up'] : ['fas', 'angle-down']"
/>
</FtButton>
</div>
<div
v-if="isProfileDropdownOpen"
tabindex="-1"
class="profileDropdown"
>
<ul
class="profileList"
>
<li
v-for="(profile, index) in profileDisplayList"
:key="index"
class="profile"
:class="{
subscribed: isProfileSubscribed(profile)
}"
:aria-labelledby="id + '-' + index"
:aria-selected="isActiveProfile(profile)"
:aria-checked="isProfileSubscribed(profile)"
tabindex="0"
role="checkbox"
@click.stop.prevent="handleSubscription(profile)"
@keydown.space.stop.prevent="handleSubscription(profile)"
>
<div
class="colorOption"
:style="{ background: profile.bgColor, color: profile.textColor }"
>
<div
class="initial"
>
{{ isProfileSubscribed(profile) ? $t('checkmark') : profileInitials[profile._id] }}
</div>
</div>
<p
:id="id + '-' + index"
class="profileName"
>
{{ profile.name }}
</p>
</li>
</ul>
</div>
</div>
</template>

<script setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { computed, ref, shallowRef } from 'vue'
import { useId } from '../../composables/use-id-polyfill'
import { useI18n } from '../../composables/use-i18n-polyfill'
import FtButton from '../../components/ft-button/ft-button.vue'
import FtPrompt from '../../components/ft-prompt/ft-prompt.vue'
import store from '../../store/index'
import { MAIN_PROFILE_ID } from '../../../constants'
import { showToast } from '../../helpers/utils'
import { getFirstCharacter } from '../../helpers/strings'
const { locale, t } = useI18n()
const props = defineProps({
channelId: {
type: String,
required: true
},
channelName: {
type: String,
required: true
},
channelThumbnail: {
type: String,
default: null
},
hideProfileDropdownToggle: {
type: Boolean,
default: false
},
openDropdownOnSubscribe: {
type: Boolean,
default: true
},
subscriptionCountText: {
type: String,
default: ''
}
})
const emit = defineEmits(['subscribed'])
const id = useId()
/**
* @typedef {object} Profile
* @property {string} _id
* @property {string} name
* @property {string} bgColor
* @property {string} textColor
* @property {object[]} subscriptions
* @property {string} subscriptions[].id
* @property {string|undefined} subscriptions[].name
* @property {string|undefined} subscriptions[].thumbnail
*/
/** @type {import('vue').ComputedRef<Profile[]>} */
const profileList = computed(() => {
return store.getters.getProfileList
})
/** @type {import('vue').ComputedRef<Profile>} */
const activeProfile = computed(() => {
return store.getters.getActiveProfile
})
const profileDisplayList = computed(() => [
profileList.value[0],
...(activeProfile.value._id !== MAIN_PROFILE_ID ? [activeProfile.value] : []),
...profileList.value.filter((profile, i) => i !== 0 && !isActiveProfile(profile) && !isProfileSubscribed(profile)),
...profileList.value.filter((profile, i) => i !== 0 && !isActiveProfile(profile) && isProfileSubscribed(profile))
])
const profileInitials = computed(() => {
const locale_ = locale.value
return profileList.value.reduce((accumulator, profile) => {
accumulator[profile._id] = profile.name
? getFirstCharacter(profile.name, locale_).toUpperCase()
: ''
return accumulator
}, {})
})
/** @type {import('vue').ComputedRef<boolean>} */
const hideChannelSubscriptions = computed(() => {
return store.getters.getHideChannelSubscriptions
})
const subscribedText = computed(() => {
let subscribedValue = (isProfileSubscribed(activeProfile.value) ? t('Channel.Unsubscribe') : t('Channel.Subscribe')).toUpperCase()
if (props.subscriptionCountText !== '' && !hideChannelSubscriptions.value) {
subscribedValue += ' ' + props.subscriptionCountText
}
return subscribedValue
})
const isProfileDropdownEnabled = computed(() => {
return !props.hideProfileDropdownToggle && profileList.value.length > 1
})
const isProfileDropdownOpen = ref(false)
/** @type {import('vue').ShallowRef<Profile | null>} */
const showUnsubscribePopupForProfile = shallowRef(null)
/**
* @param {Profile} profile
*/
function handleSubscription(profile) {
if (props.channelId === '') {
return
}
if (isProfileSubscribed(profile)) {
if (store.getters.getUnsubscriptionPopupStatus) {
showUnsubscribePopupForProfile.value = profile
} else {
handleUnsubscription(profile)
}
} else {
const profileIds = [profile._id]
if (profile._id !== MAIN_PROFILE_ID) {
const primaryProfile = profileList.value.find(prof => {
return prof._id === MAIN_PROFILE_ID
})
if (!isProfileSubscribed(primaryProfile)) {
profileIds.push(MAIN_PROFILE_ID)
}
}
store.dispatch('addChannelToProfiles', {
channel: {
id: props.channelId,
name: props.channelName,
thumbnail: props.channelThumbnail
},
profileIds
})
showToast(t('Channel.Added channel to your subscriptions'))
emit('subscribed')
}
if (isProfileDropdownEnabled.value && props.openDropdownOnSubscribe && !isProfileDropdownOpen.value) {
toggleProfileDropdown()
}
}
const subscribeButton = ref(null)
function handleProfileDropdownFocusOut() {
if (!subscribeButton.value.matches(':focus-within')) {
isProfileDropdownOpen.value = false
}
}
function toggleProfileDropdown() {
isProfileDropdownOpen.value = !isProfileDropdownOpen.value
}
/**
* @param {'yes' | 'no' | null} value
*/
function handleUnsubscribeConfirmation(value) {
const profile = showUnsubscribePopupForProfile.value
showUnsubscribePopupForProfile.value = null
if (value === 'yes') {
handleUnsubscription(profile)
}
}
/**
* @param {Profile} profile
*/
function handleUnsubscription(profile) {
const profileIds = [profile._id]
if (profile._id === MAIN_PROFILE_ID) {
// Check if a subscription exists in a different profile.
// Remove from there as well.
profileList.value.forEach((profileInList) => {
if (profileInList._id === MAIN_PROFILE_ID) {
return
}
if (isProfileSubscribed(profileInList)) {
profileIds.push(profileInList._id)
}
})
}
store.dispatch('removeChannelFromProfiles', { channelId: props.channelId, profileIds })
showToast(t('Channel.Channel has been removed from your subscriptions'))
if (profile._id === MAIN_PROFILE_ID && profileIds.length > 1) {
showToast(t('Channel.Removed subscription from {count} other channel(s)', { count: profileIds.length - 1 }))
}
}
/**
* @param {Profile} profile
*/
function isActiveProfile(profile) {
return profile._id === activeProfile.value._id
}
/**
* @param {Profile} profile
*/
function isProfileSubscribed(profile) {
const channelId = props.channelId
return profile.subscriptions.some((channel) => channel.id === channelId)
}
</script>

<style scoped lang="scss" src="./FtSubscribeButton.scss" />
Loading

0 comments on commit a777044

Please sign in to comment.