diff --git a/src/constants.js b/src/constants.js index 3224725035b5b..2e2ad56bc324e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -76,9 +76,14 @@ const SyncEvents = { // Utils const MAIN_PROFILE_ID = 'allChannels' +const MiscConstants = { + CHANNEL_IMAGE_BROKEN: '_', + CHANNEL_IMAGE_NOT_EXISTENT: '//' +} export { IpcChannels, DBActions, SyncEvents, - MAIN_PROFILE_ID + MAIN_PROFILE_ID, + MiscConstants } diff --git a/src/renderer/assets/img/loading-spinner.svg b/src/renderer/assets/img/loading-spinner.svg new file mode 100644 index 0000000000000..ee97ae8972d38 --- /dev/null +++ b/src/renderer/assets/img/loading-spinner.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js index 9cda4bad1504c..cc9404e7207fe 100644 --- a/src/renderer/components/data-settings/data-settings.js +++ b/src/renderer/components/data-settings/data-settings.js @@ -5,11 +5,11 @@ import FtButton from '../ft-button/ft-button.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtPrompt from '../ft-prompt/ft-prompt.vue' import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' + import { MAIN_PROFILE_ID } from '../../../constants' import { calculateColorLuminance, getRandomColor } from '../../helpers/colors' import { - copyToClipboard, deepCopy, escapeHTML, getTodayDateStrLocalTimezone, @@ -19,8 +19,6 @@ import { showToast, writeFileFromDialog, } from '../../helpers/utils' -import { invidiousAPICall } from '../../helpers/api/invidious' -import { getLocalChannel } from '../../helpers/api/local' export default defineComponent({ name: 'DataSettings', @@ -107,6 +105,7 @@ export default defineComponent({ showToast(`${message}: ${err}`) return } + response.filePaths.forEach(filePath => { if (filePath.endsWith('.csv')) { this.importCsvYouTubeSubscriptions(textDecode) @@ -214,13 +213,9 @@ export default defineComponent({ return sub !== '' }) const subscriptions = [] - const errorList = [] - - showToast(this.$t('Settings.Data Settings.This might take a while, please wait')) this.updateShowProgressBar(true) this.setProgressBarPercentage(0) - let count = 0 const splitCSVRegex = /(?:,|\n|^)("(?:(?:"")|[^"])*"|[^\n",]*|(?:\n|$))/g @@ -235,93 +230,58 @@ export default defineComponent({ }).filter(channel => { return channel.length > 0 }) - new Promise((resolve) => { - let finishCount = 0 - ytsubs.forEach(async (yt) => { - const { subscription, result } = await this.subscribeToChannel({ - channelId: yt[0], - subscriptions: subscriptions, - channelName: yt[2], - count: count++, - total: ytsubs.length - }) - if (result === 1) { - subscriptions.push(subscription) - } else if (result === -1) { - errorList.push(yt) - } - finishCount++ - if (finishCount === ytsubs.length) { - resolve(true) + + ytsubs.forEach((yt) => { + const channelId = yt[0] + if (!this.isChannelSubscribed(channelId, subscriptions)) { + const subscription = { + id: channelId, + name: yt[2], + thumbnail: null } - }) - }).then(_ => { - this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) - this.updateProfile(this.primaryProfile) - if (errorList.length !== 0) { - errorList.forEach(e => { // log it to console for now, dedicated tab for 'error' channels needed - console.error(`failed to import ${e[2]}. Url to channel: ${e[1]}.`) - }) - showToast(this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')) - } else { - showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) + subscriptions.push(subscription) } - }).finally(_ => { - this.updateShowProgressBar(false) }) + + this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) + this.updateProfile(this.primaryProfile) + showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) + this.updateShowProgressBar(false) }, importYouTubeSubscriptions: async function (textDecode) { const subscriptions = [] - const errorList = [] - - showToast(this.$t('Settings.Data Settings.This might take a while, please wait')) + let count = 0 this.updateShowProgressBar(true) this.setProgressBarPercentage(0) - let count = 0 - new Promise((resolve) => { - let finishCount = 0 - textDecode.forEach(async (channel) => { - const snippet = channel.snippet - if (typeof snippet === 'undefined') { - const message = this.$t('Settings.Data Settings.Invalid subscriptions file') - showToast(message) - throw new Error('Unable to find channel data') - } - const { subscription, result } = await this.subscribeToChannel({ - channelId: snippet.resourceId.channelId, - subscriptions: subscriptions, - channelName: snippet.title, - thumbnail: snippet.thumbnails.default.url, - count: count++, - total: textDecode.length - }) - if (result === 1) { - subscriptions.push(subscription) - } else if (result === -1) { - errorList.push([snippet.resourceId.channelId, `https://www.youtube.com/channel/${snippet.resourceId.channelId}`, snippet.title]) - } - finishCount++ - if (finishCount === textDecode.length) { - resolve(true) - } - }) - }).then(_ => { - this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) - this.updateProfile(this.primaryProfile) - if (errorList.length !== 0) { - errorList.forEach(e => { // log it to console for now, dedicated tab for 'error' channels needed - console.error(`failed to import ${e[2]}. Url to channel: ${e[1]}.`) + textDecode.forEach((channel) => { + const snippet = channel.snippet + if (typeof snippet === 'undefined') { + const message = this.$t('Settings.Data Settings.Invalid subscriptions file') + showToast(message) + throw new Error('Unable to find channel data') + } + + const channelId = snippet.resourceId.channelId + if (!this.isChannelSubscribed(channelId, subscriptions)) { + subscriptions.push({ + id: channelId, + name: snippet.title, + thumbnail: snippet.thumbnails.default.url }) - showToast(this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')) - } else { - showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) } - }).finally(_ => { - this.updateShowProgressBar(false) + count++ + + const progressPercentage = (count / (textDecode.length - 1)) * 100 + this.setProgressBarPercentage(progressPercentage) }) + + this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) + this.updateProfile(this.primaryProfile) + showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) + this.updateShowProgressBar(false) }, importOpmlYouTubeSubscriptions: async function (data) { @@ -359,15 +319,14 @@ export default defineComponent({ const subscriptions = [] - showToast(this.$t('Settings.Data Settings.This might take a while, please wait')) - this.updateShowProgressBar(true) this.setProgressBarPercentage(0) let count = 0 - feedData.forEach(async (channel) => { + feedData.forEach((channel) => { const xmlUrl = channel.getAttribute('xmlUrl') + const channelName = channel.getAttribute('title') let channelId if (xmlUrl.includes('https://www.youtube.com/feeds/videos.xml?channel_id=')) { channelId = new URL(xmlUrl).searchParams.get('channel_id') @@ -377,51 +336,31 @@ export default defineComponent({ } else { console.error(`Unknown xmlUrl format: ${xmlUrl}`) } - const subExists = this.primaryProfile.subscriptions.findIndex((sub) => { - return sub.id === channelId - }) - if (subExists === -1) { - let channelInfo - if (this.backendPreference === 'invidious') { - channelInfo = await this.getChannelInfoInvidious(channelId) - } else { - channelInfo = await this.getChannelInfoLocal(channelId) - } - if (typeof channelInfo.author !== 'undefined') { - const subscription = { - id: channelId, - name: channelInfo.author, - thumbnail: channelInfo.authorThumbnails[1].url - } - subscriptions.push(subscription) + if (!this.isChannelSubscribed(channelId, subscriptions)) { + const subscription = { + id: channelId, + name: channelName, + thumbnail: null } + subscriptions.push(subscription) } count++ const progressPercentage = (count / feedData.length) * 100 this.setProgressBarPercentage(progressPercentage) - - if (count === feedData.length) { - this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) - this.updateProfile(this.primaryProfile) - - if (subscriptions.length < count) { - showToast(this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')) - } else { - showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) - } - - this.updateShowProgressBar(false) - } }) + + this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) + this.updateProfile(this.primaryProfile) + showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) + this.updateShowProgressBar(false) }, importNewPipeSubscriptions: async function (newPipeData) { if (typeof newPipeData.subscriptions === 'undefined') { showToast(this.$t('Settings.Data Settings.Invalid subscriptions file')) - return } @@ -430,51 +369,32 @@ export default defineComponent({ }) const subscriptions = [] - const errorList = [] - - showToast(this.$t('Settings.Data Settings.This might take a while, please wait')) this.updateShowProgressBar(true) this.setProgressBarPercentage(0) let count = 0 - new Promise((resolve) => { - let finishCount = 0 - newPipeSubscriptions.forEach(async (channel, index) => { - const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '') - const { subscription, result } = await this.subscribeToChannel({ - channelId: channelId, - subscriptions: subscriptions, - channelName: channel.name, - count: count++, - total: newPipeSubscriptions.length - }) - if (result === 1) { - subscriptions.push(subscription) - } - if (result === -1) { - errorList.push([channelId, channel.url, channel.name]) - } - finishCount++ - if (finishCount === newPipeSubscriptions.length) { - resolve(true) - } - }) - }).then(_ => { - this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) - this.updateProfile(this.primaryProfile) - if (errorList.count > 0) { - errorList.forEach(e => { // log it to console for now, dedicated tab for 'error' channels needed - console.error(`failed to import ${e[2]}. Url to channel: ${e[1]}.`) + newPipeSubscriptions.forEach((channel) => { + const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '') + + if (!this.isChannelSubscribed(channelId, subscriptions)) { + subscriptions.push({ + id: channelId, + name: channel.name, + thumbnail: null }) - showToast(this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')) - } else { - showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) } - }).finally(_ => { - this.updateShowProgressBar(false) + + count++ + + const progressPercentage = (count / (newPipeSubscriptions.length - 1)) * 100 + this.setProgressBarPercentage(progressPercentage) }) + this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions) + this.updateProfile(this.primaryProfile) + showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported')) + this.updateShowProgressBar(false) }, exportSubscriptions: function (option) { @@ -620,9 +540,10 @@ export default defineComponent({ let exportText = 'Channel ID,Channel URL,Channel title\n' this.profileList[0].subscriptions.forEach((channel) => { const channelUrl = `https://www.youtube.com/channel/${channel.id}` - let channelName = channel.name - if (channelName.search(',') !== -1) { // add quotations and escape existing quotations if channel has comma in name - channelName = `"${channelName.replaceAll('"', '""')}"` + // escape quotations in channel name + let channelName = channel.name.replaceAll('"', '""') + if (channelName.search(',') !== -1) { // put channel name inside quotations if there's a comma in the name + channelName = `"${channelName}"` } exportText += `${channel.id},${channelUrl},${channelName}\n` }) @@ -1136,105 +1057,6 @@ export default defineComponent({ showToast(successMessage) }, - getChannelInfoInvidious: function (channelId) { - return new Promise((resolve, reject) => { - const subscriptionsPayload = { - resource: 'channels', - id: channelId, - params: {} - } - - invidiousAPICall(subscriptionsPayload).then((response) => { - resolve(response) - }).catch((err) => { - const errorMessage = this.$t('Invidious API Error (Click to copy)') - showToast(`${errorMessage}: ${err}`, 10000, () => { - copyToClipboard(err) - }) - - if (process.env.SUPPORTS_LOCAL_API && this.backendFallback && this.backendPreference === 'invidious') { - showToast(this.$t('Falling back to Local API')) - resolve(this.getChannelInfoLocal(channelId)) - } else { - resolve([]) - } - }) - }) - }, - - getChannelInfoLocal: async function (channelId) { - try { - const channel = await getLocalChannel(channelId) - - if (channel.alert) { - return [] - } - - return { - author: channel.header.author.name, - authorThumbnails: channel.header.author.thumbnails - } - } catch (err) { - console.error(err) - const errorMessage = this.$t('Local API Error (Click to copy)') - showToast(`${errorMessage}: ${err}`, 10000, () => { - copyToClipboard(err) - }) - - if (this.backendFallback && this.backendPreference === 'local') { - showToast(this.$t('Falling back to Invidious API')) - return await this.getChannelInfoInvidious(channelId) - } else { - return [] - } - } - }, - - /* - TODO: allow default thumbnail to be used to limit requests to YouTube - (thumbnail will get updated when user goes to their channel page) - Returns: - -1: an error occured - 0: already subscribed - 1: successfully subscribed - */ - async subscribeToChannel({ channelId, subscriptions, channelName = null, thumbnail = null, count = 0, total = 0 }) { - let result = 1 - if (this.isChannelSubscribed(channelId, subscriptions)) { - return { subscription: null, successMessage: 0 } - } - - let channelInfo - let subscription = null - if (channelName === null || thumbnail === null) { - try { - if (this.backendPreference === 'invidious') { - channelInfo = await this.getChannelInfoInvidious(channelId) - } else { - channelInfo = await this.getChannelInfoLocal(channelId) - } - } catch (err) { - console.error(err) - result = -1 - } - } else { - channelInfo = { author: channelName, authorThumbnails: [null, { url: thumbnail }] } - } - - if (typeof channelInfo.author !== 'undefined') { - subscription = { - id: channelId, - name: channelInfo.author, - thumbnail: channelInfo.authorThumbnails[1].url - } - } else { - result = -1 - } - const progressPercentage = (count / (total - 1)) * 100 - this.setProgressBarPercentage(progressPercentage) - return { subscription, result } - }, - isChannelSubscribed(channelId, subscriptions) { if (channelId === null) { return true } const subExists = this.primaryProfile.subscriptions.findIndex((sub) => { 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 701c0eb961aff..b60938134e770 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css @@ -50,3 +50,26 @@ text-overflow: ellipsis; inline-size: 100%; } + +/* + display loading image when we dont have a channel thumbnail yet +*/ +.bubble[src="//"] { + content: url('../../assets/img/loading-spinner.svg'); + animation: rotation 1s infinite linear; +} + +@media (prefers-reduced-motion) { + .bubble[src="//"] { + animation: rotation 2s infinite linear; + } +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js index c9854b34d6eb6..f84d17309f01f 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.js @@ -1,4 +1,5 @@ import { defineComponent } from 'vue' +import { MiscConstants } from '../../../constants' export default defineComponent({ name: 'FtChannelBubble', @@ -22,7 +23,7 @@ export default defineComponent({ }, data: function () { return { - selected: false + selected: false, } }, computed: { @@ -30,6 +31,9 @@ export default defineComponent({ return 'channelBubble' + this.channelId } }, + created () { + this.CHANNEL_IMAGE_BROKEN = MiscConstants.CHANNEL_IMAGE_BROKEN + }, methods: { handleClick: function (event) { if (event instanceof KeyboardEvent) { diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue index 53abe2f282c36..815a4a8ab8d7d 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue @@ -6,10 +6,16 @@ :to="`/channel/${channelId}`" > +
+
{ return a.name?.toLowerCase().localeCompare(b.name?.toLowerCase(), this.locale) }) - - if (this.backendPreference === 'invidious') { - subscriptions.forEach((channel) => { + subscriptions.forEach((channel) => { + if (channel.thumbnail === null) { + this.updateThumbnail(channel) + } else if (this.backendPreference === 'invidious') { channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance) - }) - } + } + }) return subscriptions }, @@ -75,5 +85,66 @@ export default defineComponent({ hiddenLabels: this.hideText } } + }, + created () { + this.CHANNEL_IMAGE_BROKEN = MiscConstants.CHANNEL_IMAGE_BROKEN + this.CHANNEL_IMAGE_NOT_EXISTENT = MiscConstants.CHANNEL_IMAGE_NOT_EXISTENT + }, + methods: { + updateThumbnail: function(channel) { + if (!this.updatingChannels.has(channel.id)) { + this.updatingChannels.add(channel.id) + this.errorCount += 1 + if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') { + // avoid too many concurrent requests + setTimeout(() => { + const existChannel = this.activeSubscriptions.find(e => e.id === channel.id) + // check again in-case someone unsubscribed + if (existChannel && existChannel.thumbnail === null) { + invidiousGetChannelInfo(channel.id).then(response => { + this.updateSubscriptionDetails({ + channelThumbnailUrl: response.authorThumbnails[0].url, + channelName: channel.name, + channelId: channel.id + }) + }).catch(() => { + this.updateSubscriptionDetails({ + channelThumbnailUrl: MiscConstants.CHANNEL_IMAGE_BROKEN, + channelName: channel.name, + channelId: channel.id + }) + }) + } + }, this.errorCount * 500) + } else { + setTimeout(() => { + const existChannel = this.activeSubscriptions.find(e => e.id === channel.id) + // check again in-case someone unsubscribed + if (existChannel && existChannel.thumbnail === null) { + getLocalChannel(channel.id).then(response => { + if (!response.alert) { + this.updateSubscriptionDetails({ + channelThumbnailUrl: response.header.author.thumbnails[0].url, + channelName: channel.name, + channelId: channel.id + }) + } else { + // channel is likely deleted. We will show a default icon instead from now on. + this.updateSubscriptionDetails({ + channelThumbnailUrl: MiscConstants.CHANNEL_IMAGE_BROKEN, + channelName: channel.name, + channelId: channel.id + }) + } + }) + } + }, this.errorCount * 500) + } + } + }, + + ...mapActions([ + 'updateSubscriptionDetails' + ]) } }) diff --git a/src/renderer/components/side-nav/side-nav.vue b/src/renderer/components/side-nav/side-nav.vue index b469246029025..bcd86cf86ca96 100644 --- a/src/renderer/components/side-nav/side-nav.vue +++ b/src/renderer/components/side-nav/side-nav.vue @@ -214,13 +214,19 @@ class="thumbnailContainer" > +

diff --git a/src/renderer/helpers/api/invidious.js b/src/renderer/helpers/api/invidious.js index 2d5c9b7263d2c..1646c98b2ee67 100644 --- a/src/renderer/helpers/api/invidious.js +++ b/src/renderer/helpers/api/invidious.js @@ -142,6 +142,19 @@ export function invidiousImageUrlToInvidious(url, currentInstance = null) { return url.replaceAll(/(\/ggpht\/)/g, `${currentInstance}/ggpht/`) } +export function invidiousImageUrlToYoutube(url) { + const ivToYt = /^.+ggpht\/(.+)/ + const ytBaseURL = 'https://yt3.ggpht.com' + if (url == null) { + return null + } + // Can be prefixed with `https://` or `//` (protocol relative) + if (url.startsWith('//')) { + url = 'https:' + url + } + return url.replace(ivToYt, `${ytBaseURL}/$1`) +} + function parseInvidiousCommentData(response) { return response.comments.map((comment) => { comment.showReplies = false diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 5f17b1920e264..209059753ef8e 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -39,6 +39,7 @@ import { parseLocalListVideo, parseLocalSubscriberCount } from '../../helpers/api/local' +import { MiscConstants } from '../../../constants' export default defineComponent({ name: 'Channel', @@ -430,6 +431,9 @@ export default defineComponent({ } } }, + created: function() { + this.CHANNEL_IMAGE_BROKEN = MiscConstants.CHANNEL_IMAGE_BROKEN + }, mounted: function () { this.isLoading = true diff --git a/src/renderer/views/Channel/Channel.vue b/src/renderer/views/Channel/Channel.vue index 73b20664dc367..47cf25b051380 100644 --- a/src/renderer/views/Channel/Channel.vue +++ b/src/renderer/views/Channel/Channel.vue @@ -29,7 +29,7 @@ class="thumbnailContainer" > { + if (channel.thumbnail === null) { + this.updateThumbnail(channel) + } + }) }, methods: { getSubscription: function () { @@ -106,21 +119,25 @@ export default defineComponent({ }, thumbnailURL: function(originalURL) { + if (originalURL == null) { return null } + let newURL = originalURL - // Sometimes relative protocol URLs are passed in - if (originalURL.startsWith('//')) { - newURL = `https:${originalURL}` - } - const hostname = new URL(newURL).hostname - if (hostname === 'yt3.ggpht.com' || hostname === 'yt3.googleusercontent.com') { - if (this.backendPreference === 'invidious') { // YT to IV - newURL = youtubeImageUrlToInvidious(newURL, this.currentInvidiousInstance) + if (newURL) { + // Sometimes relative protocol URLs are passed in + if (newURL.startsWith('//')) { + newURL = `https:${newURL}` } - } else { - if (this.backendPreference === 'local') { // IV to YT - newURL = newURL.replace(this.re.ivToYt, `${this.ytBaseURL}/$1`) - } else { // IV to IV - newURL = invidiousImageUrlToInvidious(newURL, this.currentInvidiousInstance) + const hostname = new URL(newURL).hostname + if (process.env.IS_ELECTRON && this.backendPreference === 'invidious') { + if (hostname === 'yt3.ggpht.com' || hostname === 'yt3.googleusercontent.com') { + newURL = youtubeImageUrlToInvidious(newURL, this.currentInvidiousInstance) + } else { + newURL = invidiousImageUrlToInvidious(newURL, this.currentInvidiousInstance) + } + } else { + if (hostname !== 'yt3.ggpht.com' && hostname !== 'yt3.googleusercontent.com') { + newURL = invidiousImageUrlToYoutube(newURL) + } } } @@ -128,30 +145,55 @@ export default defineComponent({ }, updateThumbnail: function(channel) { - this.errorCount += 1 - if (this.backendPreference === 'local') { - // avoid too many concurrent requests - setTimeout(() => { - getLocalChannel(channel.id).then(response => { - if (!response.alert) { - this.updateSubscriptionDetails({ - channelThumbnailUrl: this.thumbnailURL(response.header.author.thumbnails[0].url), - channelName: channel.name, - channelId: channel.id - }) - } - }) - }, this.errorCount * 500) - } else { - setTimeout(() => { - invidiousGetChannelInfo(channel.id).then(response => { - this.updateSubscriptionDetails({ - channelThumbnailUrl: this.thumbnailURL(response.authorThumbnails[0].url), - channelName: channel.name, - channelId: channel.id - }) - }) - }, this.errorCount * 500) + if (this.hideActiveSubscriptions) { + if (!this.updatingChannels.has(channel.id)) { + this.updatingChannels.add(channel.id) + this.errorCount += 1 + if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') { + // avoid too many concurrent requests + setTimeout(() => { + const existChannel = this.activeSubscriptions.find(e => e.id === channel.id) + // check again in-case someone unsubscribed + if (existChannel && existChannel.thumbnail === null) { + invidiousGetChannelInfo(channel.id).then(response => { + this.updateSubscriptionDetails({ + channelThumbnailUrl: response.authorThumbnails[0].url, + channelName: channel.name, + channelId: channel.id + }) + }).catch(e => { + this.updateSubscriptionDetails({ + channelThumbnailUrl: MiscConstants.CHANNEL_IMAGE_BROKEN, + channelName: channel.name, + channelId: channel.id + }) + }) + } + }, this.errorCount * 500) + } else { + setTimeout(() => { + const existChannel = this.activeSubscriptions.find(e => e.id === channel.id) + // check again in-case someone unsubscribed + if (existChannel && existChannel.thumbnail === null) { + getLocalChannel(channel.id).then(response => { + if (!response.alert) { + this.updateSubscriptionDetails({ + channelThumbnailUrl: response.header.author.thumbnails[0].url, + channelName: channel.name, + channelId: channel.id + }) + } else { + this.updateSubscriptionDetails({ + channelThumbnailUrl: MiscConstants.CHANNEL_IMAGE_BROKEN, + channelName: channel.name, + channelId: channel.id + }) + } + }) + } + }, this.errorCount * 500) + } + } } }, diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue index 8a902fdba59e3..e56828af61f3c 100644 --- a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue +++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue @@ -35,11 +35,17 @@ :to="`/channel/${channel.id}`" > +