Add subscription manager within profile settings. Add Upcoming video information. Other changes
This commit is contained in:
		
							parent
							
								
									20b379c269
								
							
						
					
					
						commit
						2a0c062915
					
				
							
								
								
									
										20
									
								
								appveyor.yml
								
								
								
								
							
							
						
						
									
										20
									
								
								appveyor.yml
								
								
								
								
							|  | @ -1,20 +0,0 @@ | ||||||
| image: Visual Studio 2017 |  | ||||||
| 
 |  | ||||||
| platform: |  | ||||||
|   - x64 |  | ||||||
| 
 |  | ||||||
| cache: |  | ||||||
|   - node_modules |  | ||||||
|   - '%USERPROFILE%\.electron' |  | ||||||
| 
 |  | ||||||
| init: |  | ||||||
|   - git config --global core.autocrlf input |  | ||||||
| 
 |  | ||||||
| install: |  | ||||||
|   - ps: Install-Product node 12 x64 |  | ||||||
|   - npm install |  | ||||||
| 
 |  | ||||||
| build_script: |  | ||||||
|   - npm run build |  | ||||||
| 
 |  | ||||||
| test: off |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| .bubblePadding { | .bubblePadding { | ||||||
|  |   position: relative; | ||||||
|   width: 100px; |   width: 100px; | ||||||
|   height: 115px; |   height: 115px; | ||||||
|   padding: 10px; |   padding: 10px; | ||||||
|  | @ -25,6 +26,20 @@ | ||||||
|   -webkit-border-radius: 200px 200px 200px 200px; |   -webkit-border-radius: 200px 200px 200px 200px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .selected { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 10px; | ||||||
|  |   background-color: rgba(0, 0, 0, 0.5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .icon { | ||||||
|  |   color: #EEEEEE; | ||||||
|  |   font-size: 25px; | ||||||
|  |   position: absolute; | ||||||
|  |   top: 12px; | ||||||
|  |   left: 12px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .channelName { | .channelName { | ||||||
|   font-size: 13px; |   font-size: 13px; | ||||||
|   height: 60px; |   height: 60px; | ||||||
|  |  | ||||||
|  | @ -7,18 +7,26 @@ export default Vue.extend({ | ||||||
|       type: String, |       type: String, | ||||||
|       required: true |       required: true | ||||||
|     }, |     }, | ||||||
|     channelId: { |  | ||||||
|       type: String, |  | ||||||
|       required: true |  | ||||||
|     }, |  | ||||||
|     channelThumbnail: { |     channelThumbnail: { | ||||||
|       type: String, |       type: String, | ||||||
|       required: true |       required: true | ||||||
|  |     }, | ||||||
|  |     showSelected: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data: function () { | ||||||
|  |     return { | ||||||
|  |       selected: false | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     goToChannel: function () { |     handleClick: function () { | ||||||
|       console.log('Go to channel') |       if (this.showSelected) { | ||||||
|  |         this.selected = !this.selected | ||||||
|  |       } | ||||||
|  |       this.$emit('click') | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -1,12 +1,21 @@ | ||||||
| <template> | <template> | ||||||
|   <div |   <div | ||||||
|     class="bubblePadding" |     class="bubblePadding" | ||||||
|     @click="goToChannel(channelId)" |     @click="handleClick" | ||||||
|   > |   > | ||||||
|     <img |     <img | ||||||
|       class="bubble" |       class="bubble" | ||||||
|       :src="channelThumbnail" |       :src="channelThumbnail" | ||||||
|     > |     > | ||||||
|  |     <div | ||||||
|  |       v-if="selected" | ||||||
|  |       class="bubble selected" | ||||||
|  |     > | ||||||
|  |     <font-awesome-icon | ||||||
|  |       icon="check" | ||||||
|  |       class="icon" | ||||||
|  |     /> | ||||||
|  |     </div> | ||||||
|     <div class="channelName"> |     <div class="channelName"> | ||||||
|       {{ channelName }} |       {{ channelName }} | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .forceTextColor .ft-input { | .forceTextColor .ft-input { | ||||||
|   color: var(--text-with-main-color); |   color: #EEEEEE; | ||||||
|   background-color: var(--primary-input-color); |   background-color: var(--primary-input-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -36,7 +36,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .forceTextColor ::-webkit-input-placeholder { | .forceTextColor ::-webkit-input-placeholder { | ||||||
|   color: var(--text-with-main-color); |   color: #EEEEEE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .inputAction { | .inputAction { | ||||||
|  | @ -58,7 +58,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .forceTextColor .inputAction { | .forceTextColor .inputAction { | ||||||
|   color: var(--text-with-main-color); |   color: #EEEEEE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .inputAction:hover { | .inputAction:hover { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | .card { | ||||||
|  |   width: 85%; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   margin-bottom: 30px; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,155 @@ | ||||||
|  | import Vue from 'vue' | ||||||
|  | import { mapActions } from 'vuex' | ||||||
|  | 
 | ||||||
|  | import FtCard from '../../components/ft-card/ft-card.vue' | ||||||
|  | import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' | ||||||
|  | import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue' | ||||||
|  | import FtButton from '../../components/ft-button/ft-button.vue' | ||||||
|  | import FtPrompt from '../../components/ft-prompt/ft-prompt.vue' | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  |   name: 'FtProfileAllChannelsList', | ||||||
|  |   components: { | ||||||
|  |     'ft-card': FtCard, | ||||||
|  |     'ft-flex-box': FtFlexBox, | ||||||
|  |     'ft-channel-bubble': FtChannelBubble, | ||||||
|  |     'ft-button': FtButton, | ||||||
|  |     'ft-prompt': FtPrompt | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     profile: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data: function () { | ||||||
|  |     return { | ||||||
|  |       channels: [], | ||||||
|  |       selectedLength: 0 | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     profileList: function () { | ||||||
|  |       return this.$store.getters.getProfileList | ||||||
|  |     }, | ||||||
|  |     selectedText: function () { | ||||||
|  |       const localeText = this.$t('Profile.$ selected') | ||||||
|  |       return localeText.replace('$', this.selectedLength) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     profile: function () { | ||||||
|  |       this.channels = [].concat(this.profileList[0].subscriptions).sort((a, b) => { | ||||||
|  |         const nameA = a.name.toLowerCase() | ||||||
|  |         const nameB = b.name.toLowerCase() | ||||||
|  |         if (nameA < nameB) { | ||||||
|  |           return -1 | ||||||
|  |         } | ||||||
|  |         if (nameA > nameB) { | ||||||
|  |           return 1 | ||||||
|  |         } | ||||||
|  |         return 0 | ||||||
|  |       }).filter((channel) => { | ||||||
|  |         const index = this.profile.subscriptions.findIndex((sub) => { | ||||||
|  |           return sub.id === channel.id | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         return index === -1 | ||||||
|  |       }).map((channel) => { | ||||||
|  |         channel.selected = false | ||||||
|  |         return channel | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   mounted: function () { | ||||||
|  |     if (typeof this.profile.subscriptions !== 'undefined') { | ||||||
|  |       this.channels = [].concat(this.profileList[0].subscriptions).sort((a, b) => { | ||||||
|  |         const nameA = a.name.toLowerCase() | ||||||
|  |         const nameB = b.name.toLowerCase() | ||||||
|  |         if (nameA < nameB) { | ||||||
|  |           return -1 | ||||||
|  |         } | ||||||
|  |         if (nameA > nameB) { | ||||||
|  |           return 1 | ||||||
|  |         } | ||||||
|  |         return 0 | ||||||
|  |       }).filter((channel) => { | ||||||
|  |         const index = this.profile.subscriptions.findIndex((sub) => { | ||||||
|  |           return sub.id === channel.id | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         return index === -1 | ||||||
|  |       }).map((channel) => { | ||||||
|  |         channel.selected = false | ||||||
|  |         return channel | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     handleChannelClick: function (index) { | ||||||
|  |       this.channels[index].selected = !this.channels[index].selected | ||||||
|  |       this.selectedLength = this.channels.filter((channel) => { | ||||||
|  |         return channel.selected | ||||||
|  |       }).length | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     addChannelToProfile: function () { | ||||||
|  |       if (this.selectedLength === 0) { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.No channel(s) have been selected') | ||||||
|  |         }) | ||||||
|  |       } else { | ||||||
|  |         const subscriptions = this.channels.filter((channel) => { | ||||||
|  |           return channel.selected | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         const profile = JSON.parse(JSON.stringify(this.profile)) | ||||||
|  |         profile.subscriptions = profile.subscriptions.concat(subscriptions) | ||||||
|  |         this.updateProfile(profile) | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.Profile has been updated') | ||||||
|  |         }) | ||||||
|  |         this.selectNone() | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     selectAll: function () { | ||||||
|  |       Object.keys(this.$refs).forEach((ref) => { | ||||||
|  |         if (typeof this.$refs[ref][0] !== 'undefined') { | ||||||
|  |           this.$refs[ref][0].selected = true | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.channels = this.channels.map((channel) => { | ||||||
|  |         channel.selected = true | ||||||
|  |         return channel | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.selectedLength = this.channels.filter((channel) => { | ||||||
|  |         return channel.selected | ||||||
|  |       }).length | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     selectNone: function () { | ||||||
|  |       Object.keys(this.$refs).forEach((ref) => { | ||||||
|  |         if (typeof this.$refs[ref][0] !== 'undefined') { | ||||||
|  |           this.$refs[ref][0].selected = false | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.channels = this.channels.map((channel) => { | ||||||
|  |         channel.selected = false | ||||||
|  |         return channel | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.selectedLength = this.channels.filter((channel) => { | ||||||
|  |         return channel.selected | ||||||
|  |       }).length | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     ...mapActions([ | ||||||
|  |       'showToast', | ||||||
|  |       'updateProfile' | ||||||
|  |     ]) | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <ft-card class="card"> | ||||||
|  |       <h2> | ||||||
|  |         {{ $t("Profile.Other Channels") }} | ||||||
|  |       </h2> | ||||||
|  |       <p> | ||||||
|  |         {{ selectedText }} | ||||||
|  |       </p> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-channel-bubble | ||||||
|  |           v-for="(channel, index) in channels" | ||||||
|  |           :key="index" | ||||||
|  |           :ref="`all-channels-${index}`" | ||||||
|  |           :channel-name="channel.name" | ||||||
|  |           :channel-thumbnail="channel.thumbnail" | ||||||
|  |           :show-selected="true" | ||||||
|  |           @click="handleChannelClick(index)" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-button | ||||||
|  |           :label="$t('Profile.Select All')" | ||||||
|  |           @click="selectAll" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           :label="$t('Profile.Select None')" | ||||||
|  |           @click="selectNone" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           :label="$t('Profile.Add Selected To Profile')" | ||||||
|  |           text-color="var(--text-with-main-color)" | ||||||
|  |           background-color="var(--primary-color)" | ||||||
|  |           @click="addChannelToProfile" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |     </ft-card> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script src="./ft-profile-all-channels-list.js" /> | ||||||
|  | <style scoped src="./ft-profile-all-channels-list.css" /> | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | .card { | ||||||
|  |   width: 85%; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,204 @@ | ||||||
|  | import Vue from 'vue' | ||||||
|  | import { mapActions } from 'vuex' | ||||||
|  | 
 | ||||||
|  | import FtCard from '../../components/ft-card/ft-card.vue' | ||||||
|  | import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' | ||||||
|  | import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue' | ||||||
|  | import FtButton from '../../components/ft-button/ft-button.vue' | ||||||
|  | import FtPrompt from '../../components/ft-prompt/ft-prompt.vue' | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  |   name: 'FtProfileChannelList', | ||||||
|  |   components: { | ||||||
|  |     'ft-card': FtCard, | ||||||
|  |     'ft-flex-box': FtFlexBox, | ||||||
|  |     'ft-channel-bubble': FtChannelBubble, | ||||||
|  |     'ft-button': FtButton, | ||||||
|  |     'ft-prompt': FtPrompt | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     profile: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true | ||||||
|  |     }, | ||||||
|  |     isMainProfile: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data: function () { | ||||||
|  |     return { | ||||||
|  |       showDeletePrompt: false, | ||||||
|  |       subscriptions: [], | ||||||
|  |       selectedLength: 0, | ||||||
|  |       componentKey: 0, | ||||||
|  |       deletePromptValues: [ | ||||||
|  |         'yes', | ||||||
|  |         'no' | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     profileList: function () { | ||||||
|  |       return this.$store.getters.getProfileList | ||||||
|  |     }, | ||||||
|  |     selectedText: function () { | ||||||
|  |       const localeText = this.$t('Profile.$ selected') | ||||||
|  |       return localeText.replace('$', this.selectedLength) | ||||||
|  |     }, | ||||||
|  |     deletePromptMessage: function () { | ||||||
|  |       if (this.isMainProfile) { | ||||||
|  |         return this.$t('Profile["This is your primary profile.  Are you sure you want to delete the selected channels?  The same channels will be deleted in any profile they are found in."]') | ||||||
|  |       } else { | ||||||
|  |         return this.$t('Profile["Are you sure you want to delete the selected channels?  This will not delete the channel from any other profile."]') | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     deletePromptNames: function () { | ||||||
|  |       return [ | ||||||
|  |         this.$t('Yes'), | ||||||
|  |         this.$t('No') | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     profile: function () { | ||||||
|  |       this.subscriptions = [].concat(this.profile.subscriptions.sort((a, b) => { | ||||||
|  |         const nameA = a.name.toLowerCase() | ||||||
|  |         const nameB = b.name.toLowerCase() | ||||||
|  |         if (nameA < nameB) { | ||||||
|  |           return -1 | ||||||
|  |         } | ||||||
|  |         if (nameA > nameB) { | ||||||
|  |           return 1 | ||||||
|  |         } | ||||||
|  |         return 0 | ||||||
|  |       }).map((channel) => { | ||||||
|  |         channel.selected = false | ||||||
|  |         return channel | ||||||
|  |       })) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   mounted: function () { | ||||||
|  |     if (typeof this.profile.subscriptions !== 'undefined') { | ||||||
|  |       this.subscriptions = [].concat(this.profile.subscriptions.sort((a, b) => { | ||||||
|  |         const nameA = a.name.toLowerCase() | ||||||
|  |         const nameB = b.name.toLowerCase() | ||||||
|  |         if (nameA < nameB) { | ||||||
|  |           return -1 | ||||||
|  |         } | ||||||
|  |         if (nameA > nameB) { | ||||||
|  |           return 1 | ||||||
|  |         } | ||||||
|  |         return 0 | ||||||
|  |       }).map((channel) => { | ||||||
|  |         channel.selected = false | ||||||
|  |         return channel | ||||||
|  |       })) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     displayDeletePrompt: function () { | ||||||
|  |       if (this.selectedLength === 0) { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.No channel(s) have been selected') | ||||||
|  |         }) | ||||||
|  |       } else { | ||||||
|  |         this.showDeletePrompt = true | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     handleDeletePromptClick: function (value) { | ||||||
|  |       if (value !== 'no' && value !== null) { | ||||||
|  |         if (this.isMainProfile) { | ||||||
|  |           const channelsToRemove = this.subscriptions.filter((channel) => { | ||||||
|  |             return channel.selected | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           this.subscriptions = this.subscriptions.filter((channel) => { | ||||||
|  |             return !channel.selected | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           this.profileList.forEach((x) => { | ||||||
|  |             const profile = JSON.parse(JSON.stringify(x)) | ||||||
|  |             profile.subscriptions = profile.subscriptions.filter((channel) => { | ||||||
|  |               const index = channelsToRemove.findIndex((y) => { | ||||||
|  |                 return y.id === channel.id | ||||||
|  |               }) | ||||||
|  | 
 | ||||||
|  |               return index === -1 | ||||||
|  |             }) | ||||||
|  |             this.updateProfile(profile) | ||||||
|  | 
 | ||||||
|  |             this.showToast({ | ||||||
|  |               message: this.$t('Profile.Profile has been updated') | ||||||
|  |             }) | ||||||
|  |             this.selectNone() | ||||||
|  |           }) | ||||||
|  |         } else { | ||||||
|  |           const profile = JSON.parse(JSON.stringify(this.profile)) | ||||||
|  | 
 | ||||||
|  |           this.subscriptions = this.subscriptions.filter((channel) => { | ||||||
|  |             return !channel.selected | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           profile.subscriptions = this.subscriptions | ||||||
|  |           this.selectedLength = 0 | ||||||
|  | 
 | ||||||
|  |           this.updateProfile(profile) | ||||||
|  | 
 | ||||||
|  |           this.showToast({ | ||||||
|  |             message: this.$t('Profile.Profile has been updated') | ||||||
|  |           }) | ||||||
|  |           this.selectNone() | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       this.showDeletePrompt = false | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     handleChannelClick: function (index) { | ||||||
|  |       this.subscriptions[index].selected = !this.subscriptions[index].selected | ||||||
|  |       this.selectedLength = this.subscriptions.filter((channel) => { | ||||||
|  |         return channel.selected | ||||||
|  |       }).length | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     selectAll: function () { | ||||||
|  |       Object.keys(this.$refs).forEach((ref) => { | ||||||
|  |         if (typeof this.$refs[ref][0] !== 'undefined') { | ||||||
|  |           this.$refs[ref][0].selected = true | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.subscriptions = this.subscriptions.map((channel) => { | ||||||
|  |         channel.selected = true | ||||||
|  |         return channel | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.selectedLength = this.subscriptions.filter((channel) => { | ||||||
|  |         return channel.selected | ||||||
|  |       }).length | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     selectNone: function () { | ||||||
|  |       Object.keys(this.$refs).forEach((ref) => { | ||||||
|  |         if (typeof this.$refs[ref][0] !== 'undefined') { | ||||||
|  |           this.$refs[ref][0].selected = false | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.subscriptions = this.subscriptions.map((channel) => { | ||||||
|  |         channel.selected = false | ||||||
|  |         return channel | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       this.selectedLength = this.subscriptions.filter((channel) => { | ||||||
|  |         return channel.selected | ||||||
|  |       }).length | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     ...mapActions([ | ||||||
|  |       'showToast', | ||||||
|  |       'updateProfile' | ||||||
|  |     ]) | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <ft-card class="card"> | ||||||
|  |       <h2> | ||||||
|  |         {{ $t("Profile.Subscription List") }} | ||||||
|  |       </h2> | ||||||
|  |       <p> | ||||||
|  |         {{ selectedText }} | ||||||
|  |       </p> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-channel-bubble | ||||||
|  |           v-for="(channel, index) in subscriptions" | ||||||
|  |           :key="index" | ||||||
|  |           :ref="`channel-${index}`" | ||||||
|  |           :channel-name="channel.name" | ||||||
|  |           :channel-thumbnail="channel.thumbnail" | ||||||
|  |           :show-selected="true" | ||||||
|  |           @click="handleChannelClick(index)" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-button | ||||||
|  |           :label="$t('Profile.Select All')" | ||||||
|  |           @click="selectAll" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           :label="$t('Profile.Select None')" | ||||||
|  |           @click="selectNone" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           :label="$t('Profile.Delete Selected')" | ||||||
|  |           text-color="var(--text-with-main-color)" | ||||||
|  |           background-color="var(--primary-color)" | ||||||
|  |           @click="displayDeletePrompt" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |     </ft-card> | ||||||
|  |     <ft-prompt | ||||||
|  |       v-if="showDeletePrompt" | ||||||
|  |       :label="deletePromptMessage" | ||||||
|  |       :option-names="deletePromptNames" | ||||||
|  |       :option-values="deletePromptValues" | ||||||
|  |       @click="handleDeletePromptClick" | ||||||
|  |     /> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script src="./ft-profile-channel-list.js" /> | ||||||
|  | <style scoped src="./ft-profile-channel-list.css" /> | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | .card { | ||||||
|  |   width: 85%; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message { | ||||||
|  |   color: var(--tertiary-text-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .profileName { | ||||||
|  |   width: 400px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .bottomMargin { | ||||||
|  |   margin-bottom: 30px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .colorOptions { | ||||||
|  |   max-width: 1000px; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   margin-bottom: 30px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .colorOption { | ||||||
|  |   width: 100px; | ||||||
|  |   height: 100px; | ||||||
|  |   margin: 10px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   border-radius: 200px 200px 200px 200px; | ||||||
|  |   -webkit-border-radius: 200px 200px 200px 200px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .initial { | ||||||
|  |   font-size: 50px; | ||||||
|  |   text-align: center; | ||||||
|  |   position: relative; | ||||||
|  |   bottom: 27px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media only screen and (max-width: 680px) { | ||||||
|  |   .card { | ||||||
|  |     width: 90%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,163 @@ | ||||||
|  | import Vue from 'vue' | ||||||
|  | import { mapActions } from 'vuex' | ||||||
|  | import FtCard from '../../components/ft-card/ft-card.vue' | ||||||
|  | import FtPrompt from '../../components/ft-prompt/ft-prompt.vue' | ||||||
|  | import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' | ||||||
|  | import FtInput from '../../components/ft-input/ft-input.vue' | ||||||
|  | import FtButton from '../../components/ft-button/ft-button.vue' | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  |   name: 'FtProfileEdit', | ||||||
|  |   components: { | ||||||
|  |     'ft-card': FtCard, | ||||||
|  |     'ft-prompt': FtPrompt, | ||||||
|  |     'ft-flex-box': FtFlexBox, | ||||||
|  |     'ft-input': FtInput, | ||||||
|  |     'ft-button': FtButton | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     profile: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true | ||||||
|  |     }, | ||||||
|  |     isNew: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   data: function () { | ||||||
|  |     return { | ||||||
|  |       showDeletePrompt: false, | ||||||
|  |       profileId: '', | ||||||
|  |       profileName: '', | ||||||
|  |       profileBgColor: '', | ||||||
|  |       profileTextColor: '', | ||||||
|  |       profileSubscriptions: [], | ||||||
|  |       deletePromptValues: [ | ||||||
|  |         'yes', | ||||||
|  |         'no' | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     colorValues: function () { | ||||||
|  |       return this.$store.getters.getColorValues | ||||||
|  |     }, | ||||||
|  |     profileInitial: function () { | ||||||
|  |       return this.profileName.slice(0, 1).toUpperCase() | ||||||
|  |     }, | ||||||
|  |     activeProfile: function () { | ||||||
|  |       return this.$store.getters.getActiveProfile | ||||||
|  |     }, | ||||||
|  |     defaultProfile: function () { | ||||||
|  |       return this.$store.getters.getDefaultProfile | ||||||
|  |     }, | ||||||
|  |     deletePromptLabel: function () { | ||||||
|  |       return `${this.$t('Profile.Are you sure you want to delete this profile?')} ${this.$t('Profile["All subscriptions will also be deleted."]')}` | ||||||
|  |     }, | ||||||
|  |     deletePromptNames: function () { | ||||||
|  |       return [ | ||||||
|  |         this.$t('Yes'), | ||||||
|  |         this.$t('No') | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     profileBgColor: async function (val) { | ||||||
|  |       this.profileTextColor = await this.calculateColorLuminance(val) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   mounted: async function () { | ||||||
|  |     this.profileId = this.$route.params.id | ||||||
|  |     this.profileName = this.profile.name | ||||||
|  |     this.profileBgColor = this.profile.bgColor | ||||||
|  |     this.profileTextColor = this.profile.textColor | ||||||
|  |     this.profileSubscriptions = this.profile.subscriptions | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     openDeletePrompt: function () { | ||||||
|  |       this.showDeletePrompt = true | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     handleDeletePrompt: function (response) { | ||||||
|  |       if (response === 'yes') { | ||||||
|  |         this.deleteProfile() | ||||||
|  |       } else { | ||||||
|  |         this.showDeletePrompt = false | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     saveProfile: function () { | ||||||
|  |       if (this.profileName === '') { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.Your profile name cannot be empty') | ||||||
|  |         }) | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       const profile = { | ||||||
|  |         name: this.profileName, | ||||||
|  |         bgColor: this.profileBgColor, | ||||||
|  |         textColor: this.profileTextColor, | ||||||
|  |         subscriptions: this.profileSubscriptions | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!this.isNew) { | ||||||
|  |         profile._id = this.profileId | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       console.log(profile) | ||||||
|  | 
 | ||||||
|  |       this.updateProfile(profile) | ||||||
|  | 
 | ||||||
|  |       if (this.isNew) { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.Profile has been created') | ||||||
|  |         }) | ||||||
|  |         this.$router.push({ | ||||||
|  |           path: '/settings/profile/' | ||||||
|  |         }) | ||||||
|  |       } else { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.Profile has been updated') | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     setDefaultProfile: function () { | ||||||
|  |       this.updateDefaultProfile(this.profileId) | ||||||
|  |       const message = this.$t('Profile.Your default profile has been set to $').replace('$', this.profileName) | ||||||
|  |       this.showToast({ | ||||||
|  |         message: message | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     deleteProfile: function () { | ||||||
|  |       this.removeProfile(this.profileId) | ||||||
|  |       const message = this.$t('Profile.Removed $ from your profiles').replace('$', this.profileName) | ||||||
|  |       this.showToast({ | ||||||
|  |         message: message | ||||||
|  |       }) | ||||||
|  |       if (this.defaultProfile === this.profileId) { | ||||||
|  |         this.updateDefaultProfile('allChannels') | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Profile.Your default profile has been changed to your primary profile') | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |       if (this.activeProfile._id === this.profileId) { | ||||||
|  |         this.updateActiveProfile('allChannels') | ||||||
|  |       } | ||||||
|  |       this.$router.push({ | ||||||
|  |         path: '/settings/profile/' | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     ...mapActions([ | ||||||
|  |       'showToast', | ||||||
|  |       'updateProfile', | ||||||
|  |       'removeProfile', | ||||||
|  |       'updateDefaultProfile', | ||||||
|  |       'updateActiveProfile', | ||||||
|  |       'calculateColorLuminance' | ||||||
|  |     ]) | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | @ -0,0 +1,99 @@ | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <ft-card class="card"> | ||||||
|  |       <h2>{{ $t("Profile.Edit Profile") }}</h2> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-input | ||||||
|  |           class="profileName" | ||||||
|  |           placeholder="Profile Name" | ||||||
|  |           :value="profileName" | ||||||
|  |           :show-arrow="false" | ||||||
|  |           @input="e => profileName = e" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <h3>{{ $t("Profile.Color Picker") }}</h3> | ||||||
|  |       <ft-flex-box | ||||||
|  |         class="bottomMargin colorOptions" | ||||||
|  |       > | ||||||
|  |         <div | ||||||
|  |           v-for="(color, index) in colorValues" | ||||||
|  |           :key="index" | ||||||
|  |           class="colorOption" | ||||||
|  |           :style="{ background: color }" | ||||||
|  |           @click="profileBgColor = color" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <ft-flex-box | ||||||
|  |         class="bottomMargin" | ||||||
|  |       > | ||||||
|  |         <div> | ||||||
|  |           <label for="colorPicker">{{ $t("Profile.Custom Color") }}</label> | ||||||
|  |           <input | ||||||
|  |             id="colorPicker" | ||||||
|  |             v-model="profileBgColor" | ||||||
|  |             type="color" | ||||||
|  |           > | ||||||
|  |         </div> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-input | ||||||
|  |           class="profileName" | ||||||
|  |           placeholder="" | ||||||
|  |           :value="profileBgColor" | ||||||
|  |           :show-arrow="false" | ||||||
|  |           :disabled="true" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <h3>{{ $t("Profile.Profile Preview") }}</h3> | ||||||
|  |       <ft-flex-box | ||||||
|  |         class="bottomMargin" | ||||||
|  |       > | ||||||
|  |         <div | ||||||
|  |           class="colorOption" | ||||||
|  |           :style="{ background: profileBgColor, color: profileTextColor }" | ||||||
|  |           style="cursor: default" | ||||||
|  |         > | ||||||
|  |           <p | ||||||
|  |             class="initial" | ||||||
|  |           > | ||||||
|  |             {{ profileInitial }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </ft-flex-box> | ||||||
|  |       <ft-flex-box> | ||||||
|  |         <ft-button | ||||||
|  |           v-if="isNew" | ||||||
|  |           :label="$t('Profile.Create Profile')" | ||||||
|  |           @click="saveProfile" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           v-if="!isNew" | ||||||
|  |           :label="$t('Profile.Update Profile')" | ||||||
|  |           @click="saveProfile" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           v-if="!isNew" | ||||||
|  |           :label="$t('Profile.Make Default Profile')" | ||||||
|  |           @click="setDefaultProfile" | ||||||
|  |         /> | ||||||
|  |         <ft-button | ||||||
|  |           v-if="profileId !== 'allChannels' && !isNew" | ||||||
|  |           :label="$t('Profile.Delete Profile')" | ||||||
|  |           text-color="var(--text-with-main-color)" | ||||||
|  |           background-color="var(--primary-color)" | ||||||
|  |           @click="openDeletePrompt" | ||||||
|  |         /> | ||||||
|  |       </ft-flex-box> | ||||||
|  |     </ft-card> | ||||||
|  |     <ft-prompt | ||||||
|  |       v-if="showDeletePrompt" | ||||||
|  |       :label="deletePromptLabel" | ||||||
|  |       :option-names="deletePromptNames" | ||||||
|  |       :option-values="deletePromptValues" | ||||||
|  |       @click="handleDeletePrompt" | ||||||
|  |     /> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script src="./ft-profile-edit.js" /> | ||||||
|  | <style scoped src="./ft-profile-edit.css" /> | ||||||
|  | @ -62,6 +62,10 @@ export default Vue.extend({ | ||||||
|     getTimestamp: { |     getTimestamp: { | ||||||
|       type: Function, |       type: Function, | ||||||
|       required: true |       required: true | ||||||
|  |     }, | ||||||
|  |     isUpcoming: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   data: function () { |   data: function () { | ||||||
|  |  | ||||||
|  | @ -67,6 +67,7 @@ | ||||||
|           @click="$emit('theatreMode')" |           @click="$emit('theatreMode')" | ||||||
|         /> |         /> | ||||||
|         <ft-icon-button |         <ft-icon-button | ||||||
|  |           v-if="!isUpcoming" | ||||||
|           :title="$t('Change Format.Change Video Formats')" |           :title="$t('Change Format.Change Video Formats')" | ||||||
|           class="option" |           class="option" | ||||||
|           theme="secondary" |           theme="secondary" | ||||||
|  |  | ||||||
|  | @ -152,6 +152,7 @@ export default Vue.extend({ | ||||||
|     $route() { |     $route() { | ||||||
|       // react to route changes...
 |       // react to route changes...
 | ||||||
|       this.id = this.$route.params.id |       this.id = this.$route.params.id | ||||||
|  |       this.currentTab = 'videos' | ||||||
|       this.isLoading = true |       this.isLoading = true | ||||||
| 
 | 
 | ||||||
|       if (!this.usingElectron) { |       if (!this.usingElectron) { | ||||||
|  | @ -224,6 +225,10 @@ export default Vue.extend({ | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  |     goToChannel: function (id) { | ||||||
|  |       this.$router.push({ path: `/channel/${id}` }) | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|     getChannelInfoLocal: function () { |     getChannelInfoLocal: function () { | ||||||
|       this.apiUsed = 'local' |       this.apiUsed = 'local' | ||||||
|       ytch.getChannelInfo(this.id).then((response) => { |       ytch.getChannelInfo(this.id).then((response) => { | ||||||
|  |  | ||||||
|  | @ -123,6 +123,7 @@ | ||||||
|             :channel-name="channel.author" |             :channel-name="channel.author" | ||||||
|             :channel-id="channel.authorId" |             :channel-id="channel.authorId" | ||||||
|             :channel-thumbnail="channel.authorThumbnails[channel.authorThumbnails.length - 1].url" |             :channel-thumbnail="channel.authorThumbnails[channel.authorThumbnails.length - 1].url" | ||||||
|  |             @click="goToChannel(channel.authorId)" | ||||||
|           /> |           /> | ||||||
|         </ft-flex-box> |         </ft-flex-box> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -1,45 +0,0 @@ | ||||||
| .card { |  | ||||||
|   width: 85%; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   margin-bottom: 60px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .message { |  | ||||||
|   color: var(--tertiary-text-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .profileName { |  | ||||||
|   width: 400px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .bottomMargin { |  | ||||||
|   margin-bottom: 30px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .colorOptions { |  | ||||||
|   max-width: 1000px; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   margin-bottom: 30px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .colorOption { |  | ||||||
|   width: 100px; |  | ||||||
|   height: 100px; |  | ||||||
|   margin: 10px; |  | ||||||
|   cursor: pointer; |  | ||||||
|   border-radius: 200px 200px 200px 200px; |  | ||||||
|   -webkit-border-radius: 200px 200px 200px 200px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .initial { |  | ||||||
|   font-size: 50px; |  | ||||||
|   text-align: center; |  | ||||||
|   position: relative; |  | ||||||
|   bottom: 27px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @media only screen and (max-width: 680px) { |  | ||||||
|   .card { |  | ||||||
|     width: 90%; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -1,62 +1,50 @@ | ||||||
| import Vue from 'vue' | import Vue from 'vue' | ||||||
| import { mapActions } from 'vuex' | import { mapActions } from 'vuex' | ||||||
| import FtLoader from '../../components/ft-loader/ft-loader.vue' | import FtLoader from '../../components/ft-loader/ft-loader.vue' | ||||||
| import FtCard from '../../components/ft-card/ft-card.vue' | import FtProfileEdit from '../../components/ft-profile-edit/ft-profile-edit.vue' | ||||||
| import FtPrompt from '../../components/ft-prompt/ft-prompt.vue' | import FtProfileChannelList from '../../components/ft-profile-channel-list/ft-profile-channel-list.vue' | ||||||
| import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' | import FtProfileAllChannelsList from '../../components/ft-profile-all-channels-list/ft-profile-all-channels-list.vue' | ||||||
| import FtInput from '../../components/ft-input/ft-input.vue' |  | ||||||
| import FtButton from '../../components/ft-button/ft-button.vue' |  | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
|   name: 'ProfileEdit', |   name: 'ProfileEdit', | ||||||
|   components: { |   components: { | ||||||
|     'ft-loader': FtLoader, |     'ft-loader': FtLoader, | ||||||
|     'ft-card': FtCard, |     'ft-profile-edit': FtProfileEdit, | ||||||
|     'ft-prompt': FtPrompt, |     'ft-profile-channel-list': FtProfileChannelList, | ||||||
|     'ft-flex-box': FtFlexBox, |     'ft-profile-all-channels-list': FtProfileAllChannelsList | ||||||
|     'ft-input': FtInput, |  | ||||||
|     'ft-button': FtButton |  | ||||||
|   }, |   }, | ||||||
|   data: function () { |   data: function () { | ||||||
|     return { |     return { | ||||||
|       isLoading: false, |       isLoading: false, | ||||||
|       showDeletePrompt: false, |  | ||||||
|       deletePromptLabel: '', |  | ||||||
|       isNew: false, |       isNew: false, | ||||||
|       profileId: '', |       profileId: '', | ||||||
|       profileName: '', |       profile: {} | ||||||
|       profileBgColor: '', |  | ||||||
|       profileTextColor: '', |  | ||||||
|       profileSubscriptions: [], |  | ||||||
|       deletePromptValues: [ |  | ||||||
|         'yes', |  | ||||||
|         'no' |  | ||||||
|       ] |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     colorValues: function () { |     profileList: function () { | ||||||
|       return this.$store.getters.getColorValues |       return this.$store.getters.getProfileList | ||||||
|     }, |     }, | ||||||
|     profileInitial: function () { |     isMainProfile: function () { | ||||||
|       return this.profileName.slice(0, 1).toUpperCase() |       return this.profileId === 'allChannels' | ||||||
|     }, |  | ||||||
|     activeProfile: function () { |  | ||||||
|       return this.$store.getters.getActiveProfile |  | ||||||
|     }, |  | ||||||
|     defaultProfile: function () { |  | ||||||
|       return this.$store.getters.getDefaultProfile |  | ||||||
|     }, |  | ||||||
|     deletePromptNames: function () { |  | ||||||
|       return [ |  | ||||||
|         this.$t('Yes'), |  | ||||||
|         this.$t('No') |  | ||||||
|       ] |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
|     profileBgColor: async function (val) { |     profileList: { | ||||||
|       this.profileTextColor = await this.calculateColorLuminance(val) |       handler: function () { | ||||||
|  |         this.grabProfileInfo(this.profileId).then((profile) => { | ||||||
|  |           if (profile === null) { | ||||||
|  |             this.showToast({ | ||||||
|  |               message: this.$t('Profile.Profile could not be found') | ||||||
|  |             }) | ||||||
|  |             this.$router.push({ | ||||||
|  |               path: '/settings/profile/' | ||||||
|  |             }) | ||||||
|  |           } | ||||||
|  |           this.profile = profile | ||||||
|  |         }) | ||||||
|  |       }, | ||||||
|  |       deep: true | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted: async function () { |   mounted: async function () { | ||||||
|  | @ -67,13 +55,18 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
|     if (profileType === 'newProfile') { |     if (profileType === 'newProfile') { | ||||||
|       this.isNew = true |       this.isNew = true | ||||||
|       this.profileBgColor = await this.getRandomColor() |       const bgColor = await this.getRandomColor() | ||||||
|  |       const textColor = await this.calculateColorLuminance(bgColor) | ||||||
|  |       this.profile = { | ||||||
|  |         name: '', | ||||||
|  |         bgColor: bgColor, | ||||||
|  |         textColor: textColor, | ||||||
|  |         subscriptions: [] | ||||||
|  |       } | ||||||
|       this.isLoading = false |       this.isLoading = false | ||||||
|     } else { |     } else { | ||||||
|       this.isNew = false |       this.isNew = false | ||||||
|       this.profileId = this.$route.params.id |       this.profileId = this.$route.params.id | ||||||
|       console.log(this.profileId) |  | ||||||
|       console.log(this.$route.name) |  | ||||||
| 
 | 
 | ||||||
|       this.grabProfileInfo(this.profileId).then((profile) => { |       this.grabProfileInfo(this.profileId).then((profile) => { | ||||||
|         if (profile === null) { |         if (profile === null) { | ||||||
|  | @ -84,100 +77,17 @@ export default Vue.extend({ | ||||||
|             path: '/settings/profile/' |             path: '/settings/profile/' | ||||||
|           }) |           }) | ||||||
|         } |         } | ||||||
|         this.profileName = profile.name |         this.profile = profile | ||||||
|         this.profileBgColor = profile.bgColor |  | ||||||
|         this.profileTextColor = profile.textColor |  | ||||||
|         this.profileSubscriptions = profile.subscriptions |  | ||||||
|         this.isLoading = false |         this.isLoading = false | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     openDeletePrompt: function () { |  | ||||||
|       this.showDeletePrompt = true |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     handleDeletePrompt: function (response) { |  | ||||||
|       if (response === 'yes') { |  | ||||||
|         this.deleteProfile() |  | ||||||
|       } else { |  | ||||||
|         this.showDeletePrompt = false |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     saveProfile: function () { |  | ||||||
|       if (this.profileName === '') { |  | ||||||
|         this.showToast({ |  | ||||||
|           message: this.$t('Profile.Your profile name cannot be empty') |  | ||||||
|         }) |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
|       const profile = { |  | ||||||
|         name: this.profileName, |  | ||||||
|         bgColor: this.profileBgColor, |  | ||||||
|         textColor: this.profileTextColor, |  | ||||||
|         subscriptions: this.profileSubscriptions |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (!this.isNew) { |  | ||||||
|         profile._id = this.profileId |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       console.log(profile) |  | ||||||
| 
 |  | ||||||
|       this.updateProfile(profile) |  | ||||||
| 
 |  | ||||||
|       if (this.isNew) { |  | ||||||
|         this.showToast({ |  | ||||||
|           message: this.$t('Profile.Profile has been created') |  | ||||||
|         }) |  | ||||||
|         this.$router.push({ |  | ||||||
|           path: '/settings/profile/' |  | ||||||
|         }) |  | ||||||
|       } else { |  | ||||||
|         this.showToast({ |  | ||||||
|           message: this.$t('Profile.Profile has been updated') |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     setDefaultProfile: function () { |  | ||||||
|       this.updateDefaultProfile(this.profileId) |  | ||||||
|       const message = this.$t('Profile.Your default profile has been set to $').replace('$', this.profileName) |  | ||||||
|       this.showToast({ |  | ||||||
|         message: message |  | ||||||
|       }) |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     deleteProfile: function () { |  | ||||||
|       this.removeProfile(this.profileId) |  | ||||||
|       const message = this.$t('Profile.Removed $ from your profiles').replace('$', this.profileName) |  | ||||||
|       this.showToast({ |  | ||||||
|         message: message |  | ||||||
|       }) |  | ||||||
|       if (this.defaultProfile === this.profileId) { |  | ||||||
|         this.updateDefaultProfile('allChannels') |  | ||||||
|         this.showToast({ |  | ||||||
|           message: this.$t('Profile.Your default profile has been changed to your primary profile') |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|       if (this.activeProfile._id === this.profileId) { |  | ||||||
|         this.updateActiveProfile('allChannels') |  | ||||||
|       } |  | ||||||
|       this.$router.push({ |  | ||||||
|         path: '/settings/profile/' |  | ||||||
|       }) |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     ...mapActions([ |     ...mapActions([ | ||||||
|       'showToast', |       'showToast', | ||||||
|       'grabProfileInfo', |       'grabProfileInfo', | ||||||
|       'updateProfile', |       'getRandomColor', | ||||||
|       'removeProfile', |       'calculateColorLuminance' | ||||||
|       'updateDefaultProfile', |  | ||||||
|       'updateActiveProfile', |  | ||||||
|       'calculateColorLuminance', |  | ||||||
|       'getRandomColor' |  | ||||||
|     ]) |     ]) | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -7,99 +7,20 @@ | ||||||
|     <div |     <div | ||||||
|       v-else |       v-else | ||||||
|     > |     > | ||||||
|       <ft-card class="card"> |       <ft-profile-edit | ||||||
|         <h2>{{ $t("Profile.Edit Profile") }}</h2> |         :profile="profile" | ||||||
|         <ft-flex-box> |         :is-new="isNew" | ||||||
|           <ft-input |       /> | ||||||
|             class="profileName" |       <ft-profile-channel-list | ||||||
|             placeholder="Profile Name" |         v-if="!isNew" | ||||||
|             :value="profileName" |         :profile="profile" | ||||||
|             :show-arrow="false" |         :is-main-profile="isMainProfile" | ||||||
|             @input="e => profileName = e" |       /> | ||||||
|           /> |       <ft-profile-all-channels-list | ||||||
|         </ft-flex-box> |         v-if="!isNew && !isMainProfile" | ||||||
|         <h3>{{ $t("Profile.Color Picker") }}</h3> |         :profile="profile" | ||||||
|         <ft-flex-box |       /> | ||||||
|           class="bottomMargin colorOptions" |  | ||||||
|         > |  | ||||||
|           <div |  | ||||||
|             v-for="(color, index) in colorValues" |  | ||||||
|             :key="index" |  | ||||||
|             class="colorOption" |  | ||||||
|             :style="{ background: color }" |  | ||||||
|             @click="profileBgColor = color" |  | ||||||
|           /> |  | ||||||
|         </ft-flex-box> |  | ||||||
|         <ft-flex-box |  | ||||||
|           class="bottomMargin" |  | ||||||
|         > |  | ||||||
|           <div> |  | ||||||
|             <label for="colorPicker">{{ $t("Profile.Custom Color") }}</label> |  | ||||||
|             <input |  | ||||||
|               id="colorPicker" |  | ||||||
|               v-model="profileBgColor" |  | ||||||
|               type="color" |  | ||||||
|             > |  | ||||||
|           </div> |  | ||||||
|         </ft-flex-box> |  | ||||||
|         <ft-flex-box> |  | ||||||
|           <ft-input |  | ||||||
|             class="profileName" |  | ||||||
|             placeholder="" |  | ||||||
|             :value="profileBgColor" |  | ||||||
|             :show-arrow="false" |  | ||||||
|             :disabled="true" |  | ||||||
|           /> |  | ||||||
|         </ft-flex-box> |  | ||||||
|         <h3>{{ $t("Profile.Profile Preview") }}</h3> |  | ||||||
|         <ft-flex-box |  | ||||||
|           class="bottomMargin" |  | ||||||
|         > |  | ||||||
|           <div |  | ||||||
|             class="colorOption" |  | ||||||
|             :style="{ background: profileBgColor, color: profileTextColor }" |  | ||||||
|             style="cursor: default" |  | ||||||
|           > |  | ||||||
|             <p |  | ||||||
|               class="initial" |  | ||||||
|             > |  | ||||||
|               {{ profileInitial }} |  | ||||||
|             </p> |  | ||||||
|           </div> |  | ||||||
|         </ft-flex-box> |  | ||||||
|         <ft-flex-box> |  | ||||||
|           <ft-button |  | ||||||
|             v-if="isNew" |  | ||||||
|             :label="$t('Profile.Create Profile')" |  | ||||||
|             @click="saveProfile" |  | ||||||
|           /> |  | ||||||
|           <ft-button |  | ||||||
|             v-if="!isNew" |  | ||||||
|             :label="$t('Profile.Update Profile')" |  | ||||||
|             @click="saveProfile" |  | ||||||
|           /> |  | ||||||
|           <ft-button |  | ||||||
|             v-if="!isNew" |  | ||||||
|             :label="$t('Profile.Make Default Profile')" |  | ||||||
|             @click="setDefaultProfile" |  | ||||||
|           /> |  | ||||||
|           <ft-button |  | ||||||
|             v-if="profileId !== 'allChannels' && !isNew" |  | ||||||
|             :label="$t('Profile.Delete Profile')" |  | ||||||
|             text-color="var(--text-with-main-color)" |  | ||||||
|             background-color="var(--primary-color)" |  | ||||||
|             @click="openDeletePrompt" |  | ||||||
|           /> |  | ||||||
|         </ft-flex-box> |  | ||||||
|       </ft-card> |  | ||||||
|     </div> |     </div> | ||||||
|     <ft-prompt |  | ||||||
|       v-if="showDeletePrompt" |  | ||||||
|       :label="deletePromptLabel" |  | ||||||
|       :option-names="deletePromptNames" |  | ||||||
|       :option-values="deletePromptValues" |  | ||||||
|       @click="handleDeletePrompt" |  | ||||||
|     /> |  | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,6 +40,8 @@ export default Vue.extend({ | ||||||
|       showYouTubeNoCookieEmbed: false, |       showYouTubeNoCookieEmbed: false, | ||||||
|       hidePlayer: false, |       hidePlayer: false, | ||||||
|       isLive: false, |       isLive: false, | ||||||
|  |       isUpcoming: false, | ||||||
|  |       upcomingTimestamp: null, | ||||||
|       activeFormat: 'legacy', |       activeFormat: 'legacy', | ||||||
|       videoId: '', |       videoId: '', | ||||||
|       videoTitle: '', |       videoTitle: '', | ||||||
|  | @ -215,8 +217,9 @@ export default Vue.extend({ | ||||||
|           this.videoLikeCount = result.videoDetails.likes |           this.videoLikeCount = result.videoDetails.likes | ||||||
|           this.videoDislikeCount = result.videoDetails.dislikes |           this.videoDislikeCount = result.videoDetails.dislikes | ||||||
|           this.isLive = result.player_response.videoDetails.isLiveContent |           this.isLive = result.player_response.videoDetails.isLiveContent | ||||||
|  |           this.isUpcoming = result.player_response.videoDetails.isUpcoming | ||||||
| 
 | 
 | ||||||
|           if (!this.isLive) { |           if (!this.isLive && !this.isUpcoming) { | ||||||
|             const captionTracks = |             const captionTracks = | ||||||
|               result.player_response.captions && |               result.player_response.captions && | ||||||
|               result.player_response.captions.playerCaptionsTracklistRenderer |               result.player_response.captions.playerCaptionsTracklistRenderer | ||||||
|  | @ -243,7 +246,7 @@ export default Vue.extend({ | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (this.isLive) { |           if (this.isLive && !this.isUpcoming) { | ||||||
|             this.enableLegacyFormat() |             this.enableLegacyFormat() | ||||||
| 
 | 
 | ||||||
|             this.videoSourceList = result.formats.filter((format) => { |             this.videoSourceList = result.formats.filter((format) => { | ||||||
|  | @ -279,28 +282,39 @@ export default Vue.extend({ | ||||||
|             } else { |             } else { | ||||||
|               this.activeSourceList = this.videoSourceList |               this.activeSourceList = this.videoSourceList | ||||||
|             } |             } | ||||||
|  |           } else if (this.isUpcoming) { | ||||||
|  |             const upcomingTimestamp = new Date(result.videoDetails.liveBroadcastDetails.startTimestamp) | ||||||
|  |             this.upcomingTimestamp = upcomingTimestamp.toLocaleString() | ||||||
|           } else { |           } else { | ||||||
|             this.videoLengthSeconds = parseInt(result.videoDetails.lengthSeconds) |             this.videoLengthSeconds = parseInt(result.videoDetails.lengthSeconds) | ||||||
|             this.videoSourceList = result.player_response.streamingData.formats |             this.videoSourceList = result.player_response.streamingData.formats | ||||||
|             this.dashSrc = await this.createLocalDashManifest(result.player_response.streamingData.adaptiveFormats) |  | ||||||
| 
 | 
 | ||||||
|             this.audioSourceList = result.player_response.streamingData.adaptiveFormats.filter((format) => { |             if (typeof result.player_response.streamingData.adaptiveFormats !== 'undefined') { | ||||||
|               return format.mimeType.includes('audio') |               this.dashSrc = await this.createLocalDashManifest(result.player_response.streamingData.adaptiveFormats) | ||||||
|             }).map((format) => { | 
 | ||||||
|               return { |               this.audioSourceList = result.player_response.streamingData.adaptiveFormats.filter((format) => { | ||||||
|                 url: format.url, |                 return format.mimeType.includes('audio') | ||||||
|                 type: format.mimeType, |               }).map((format) => { | ||||||
|                 label: 'Audio', |                 return { | ||||||
|                 qualityLabel: format.bitrate |                   url: format.url, | ||||||
|  |                   type: format.mimeType, | ||||||
|  |                   label: 'Audio', | ||||||
|  |                   qualityLabel: format.bitrate | ||||||
|  |                 } | ||||||
|  |               }).sort((a, b) => { | ||||||
|  |                 return a.qualityLabel - b.qualityLabel | ||||||
|  |               }) | ||||||
|  | 
 | ||||||
|  |               if (this.activeFormat === 'audio') { | ||||||
|  |                 this.activeSourceList = this.audioSourceList | ||||||
|  |               } else { | ||||||
|  |                 this.activeSourceList = this.videoSourceList | ||||||
|               } |               } | ||||||
|             }).sort((a, b) => { |  | ||||||
|               return a.qualityLabel - b.qualityLabel |  | ||||||
|             }) |  | ||||||
| 
 |  | ||||||
|             if (this.activeFormat === 'audio') { |  | ||||||
|               this.activeSourceList = this.audioSourceList |  | ||||||
|             } else { |             } else { | ||||||
|               this.activeSourceList = this.videoSourceList |               this.activeSourceList = this.videoSourceList | ||||||
|  |               this.audioSourceList = null | ||||||
|  |               this.dashSrc = null | ||||||
|  |               this.enableLegacyFormat() | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (typeof result.player_response.storyboards !== 'undefined') { |             if (typeof result.player_response.storyboards !== 'undefined') { | ||||||
|  | @ -528,6 +542,13 @@ export default Vue.extend({ | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       if (this.dashSrc === null) { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Change Format.Dash formats are not available for this video') | ||||||
|  |         }) | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       this.activeFormat = 'dash' |       this.activeFormat = 'dash' | ||||||
|       this.hidePlayer = true |       this.hidePlayer = true | ||||||
| 
 | 
 | ||||||
|  | @ -555,6 +576,13 @@ export default Vue.extend({ | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       if (this.audioSourceList === null) { | ||||||
|  |         this.showToast({ | ||||||
|  |           message: this.$t('Change Format.Audio formats are not available for this video') | ||||||
|  |         }) | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       this.activeFormat = 'audio' |       this.activeFormat = 'audio' | ||||||
|       this.activeSourceList = this.audioSourceList |       this.activeSourceList = this.audioSourceList | ||||||
|       this.hidePlayer = true |       this.hidePlayer = true | ||||||
|  |  | ||||||
|  | @ -35,6 +35,30 @@ | ||||||
|       grid-column: 1 |       grid-column: 1 | ||||||
|       max-width: calc(80vh * 1.78) |       max-width: calc(80vh * 1.78) | ||||||
|       margin: 0 auto |       margin: 0 auto | ||||||
|  |       position: relative | ||||||
|  | 
 | ||||||
|  |       .upcomingThumbnail | ||||||
|  |         width: 100% | ||||||
|  | 
 | ||||||
|  |       .premiereDate | ||||||
|  |         color: #FFFFFF | ||||||
|  |         background-color: rgba(0, 0, 0, 0.7) | ||||||
|  |         width: 400px | ||||||
|  |         height: 60px | ||||||
|  |         border-radius: 5% | ||||||
|  |         position: absolute | ||||||
|  |         bottom: 5px | ||||||
|  | 
 | ||||||
|  |         .premiereIcon | ||||||
|  |           float: left | ||||||
|  |           font-size: 25px | ||||||
|  |           margin-top: 12px | ||||||
|  |           margin-left: 8px | ||||||
|  |           padding: 5px | ||||||
|  | 
 | ||||||
|  |         .premiereText | ||||||
|  |           margin-left: 50px | ||||||
|  |           margin-top: 10px | ||||||
| 
 | 
 | ||||||
|   .watchVideo |   .watchVideo | ||||||
|     margin: 0px 8px 16px |     margin: 0px 8px 16px | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ | ||||||
|     <div class="videoArea"> |     <div class="videoArea"> | ||||||
|       <div class="videoAreaMargin"> |       <div class="videoAreaMargin"> | ||||||
|         <ft-video-player |         <ft-video-player | ||||||
|           v-if="!isLoading && !hidePlayer" |           v-if="!isLoading && !hidePlayer && !isUpcoming" | ||||||
|           ref="videoPlayer" |           ref="videoPlayer" | ||||||
|           :dash-src="dashSrc" |           :dash-src="dashSrc" | ||||||
|           :source-list="activeSourceList" |           :source-list="activeSourceList" | ||||||
|  | @ -27,6 +27,30 @@ | ||||||
|           @ended="handleVideoEnded" |           @ended="handleVideoEnded" | ||||||
|           @error="handleVideoError" |           @error="handleVideoError" | ||||||
|         /> |         /> | ||||||
|  |         <div | ||||||
|  |           v-if="!isLoading && isUpcoming" | ||||||
|  |           class="videoPlayer" | ||||||
|  |         > | ||||||
|  |           <img | ||||||
|  |             :src="thumbnail" | ||||||
|  |             class="upcomingThumbnail" | ||||||
|  |           /> | ||||||
|  |           <div | ||||||
|  |             class='premiereDate' | ||||||
|  |           > | ||||||
|  |             <font-awesome-icon | ||||||
|  |               icon="satellite-dish" | ||||||
|  |               class="premiereIcon" | ||||||
|  |             /> | ||||||
|  |             <p | ||||||
|  |               class="premiereText" | ||||||
|  |             > | ||||||
|  |               Premieres on: | ||||||
|  |               <br /> | ||||||
|  |               {{ upcomingTimestamp }} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="infoArea"> |     <div class="infoArea"> | ||||||
|  | @ -43,6 +67,7 @@ | ||||||
|         :dislike-count="videoDislikeCount" |         :dislike-count="videoDislikeCount" | ||||||
|         :view-count="videoViewCount" |         :view-count="videoViewCount" | ||||||
|         :get-timestamp="getTimestamp" |         :get-timestamp="getTimestamp" | ||||||
|  |         :is-upcoming="isUpcoming" | ||||||
|         class="watchVideo" |         class="watchVideo" | ||||||
|         :class="{ theatreWatchVideo: useTheatreMode }" |         :class="{ theatreWatchVideo: useTheatreMode }" | ||||||
|         @theatreMode="toggleTheatreMode" |         @theatreMode="toggleTheatreMode" | ||||||
|  |  | ||||||
|  | @ -306,6 +306,16 @@ Profile: | ||||||
|   Your default profile has been changed to your primary profile: Your default profile |   Your default profile has been changed to your primary profile: Your default profile | ||||||
|     has been changed to your primary profile |     has been changed to your primary profile | ||||||
|   $ is now the active profile: $ is now the active profile |   $ is now the active profile: $ is now the active profile | ||||||
|  |   Subscription List: Subscription List | ||||||
|  |   Other Channels: Other Channels | ||||||
|  |   $ selected: $ selected | ||||||
|  |   Select All: Select All | ||||||
|  |   Select None: Select None | ||||||
|  |   Delete Selected: Delete Selected | ||||||
|  |   Add Selected To Profile: Add Selected To Profile | ||||||
|  |   No channel(s) have been selected: No channel(s) have been selected | ||||||
|  |   This is your primary profile.  Are you sure you want to delete the selected channels?  The same channels will be deleted in any profile they are found in.: This is your primary profile.  Are you sure you want to delete the selected channels?  The same channels will be deleted in any profile they are found in. | ||||||
|  |   Are you sure you want to delete the selected channels?  This will not delete the channel from any other profile.: Are you sure you want to delete the selected channels?  This will not delete the channel from any other profile. | ||||||
| #On Channel Page | #On Channel Page | ||||||
| Channel: | Channel: | ||||||
|   Subscriber: Subscriber |   Subscriber: Subscriber | ||||||
|  | @ -435,6 +445,8 @@ Change Format: | ||||||
|   Use Dash Formats: Use Dash Formats |   Use Dash Formats: Use Dash Formats | ||||||
|   Use Legacy Formats: Use Legacy Formats |   Use Legacy Formats: Use Legacy Formats | ||||||
|   Use Audio Formats: Use Audio Formats |   Use Audio Formats: Use Audio Formats | ||||||
|  |   Dash formats are not available for this video: Dash formats are not available for this video | ||||||
|  |   Audio formats are not available for this video: Audio formats are not available for this video | ||||||
| Share: | Share: | ||||||
|   Share Video: Share Video |   Share Video: Share Video | ||||||
|   Include Timestamp: Include Timestamp |   Include Timestamp: Include Timestamp | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue