From ab3c1b9b29e93685a9c353451bc269334da2793c Mon Sep 17 00:00:00 2001 From: Jason <84899178+jasonhenriquez@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:54:46 +0000 Subject: [PATCH] Show when Subscriptions / Trending / Most Popular were last updated (#4380) * Implement first draft of last subscription refresh timestamp * Update styling to be a top bar * Update styling to be banner-compatible, & increase banner X button size on mobile * Update subscription refresh timestamp to be relative * Implement refresh timestamps for Shorts, Live, and Community tabs * Extract refresh widget to its own component * Add Trending and Popular refresh widgets with timestamps * Fix justifying when no timestamp exists * Move timestamps to utils store * Remove unneeded ref classes and currentLocale computed property * Add page-specific titles for each feed type * Implement showing least recent cache date per profile * Update styling property placement & match top nav box shadow on ft-refresh-widget * Implement showing timestamp for profile only if all channel subscriptions can be found in cache * Disable refresh button instead of removing it or the widget from the DOM * Increase top banner's top margin * Update channel caching calls to provide timestamps * Modify updateCacheByChannel functions to have default timestamp of new Date() * Fix 30-day month relative date calculation scenarios through new optional parameter * Rectify Case 3 (see https://github.com/FreeTubeApp/FreeTube/pull/3668) * Add back missing line in Popular.js --- src/renderer/App.css | 10 ++- .../ft-icon-button/ft-icon-button.js | 6 ++ .../ft-icon-button/ft-icon-button.scss | 6 ++ .../ft-icon-button/ft-icon-button.vue | 3 +- .../components/ft-list-video/ft-list-video.js | 55 ++------------ .../ft-notification-banner.css | 8 +++ .../ft-notification-banner.vue | 2 +- .../ft-refresh-widget/ft-refresh-widget.css | 36 ++++++++++ .../ft-refresh-widget/ft-refresh-widget.js | 29 ++++++++ .../ft-refresh-widget/ft-refresh-widget.vue | 27 +++++++ .../subscriptions-community.js | 28 ++++++-- .../subscriptions-community.vue | 2 + .../subscriptions-live/subscriptions-live.js | 21 +++++- .../subscriptions-live/subscriptions-live.vue | 2 + .../subscriptions-shorts.js | 22 +++++- .../subscriptions-shorts.vue | 2 + .../subscriptions-tab-ui.css | 12 ---- .../subscriptions-tab-ui.js | 14 +++- .../subscriptions-tab-ui.vue | 11 ++- .../subscriptions-videos.js | 25 ++++++- .../subscriptions-videos.vue | 2 + src/renderer/helpers/utils.js | 57 +++++++++++++++ src/renderer/store/modules/subscriptions.js | 12 ++-- src/renderer/store/modules/utils.js | 72 ++++++++++++++++++- src/renderer/views/Popular/Popular.css | 12 ---- src/renderer/views/Popular/Popular.js | 17 ++++- src/renderer/views/Popular/Popular.vue | 10 ++- src/renderer/views/Trending/Trending.css | 12 ---- src/renderer/views/Trending/Trending.js | 17 ++++- src/renderer/views/Trending/Trending.vue | 10 ++- static/locales/en-US.yaml | 5 +- 31 files changed, 412 insertions(+), 135 deletions(-) create mode 100644 src/renderer/components/ft-refresh-widget/ft-refresh-widget.css create mode 100644 src/renderer/components/ft-refresh-widget/ft-refresh-widget.js create mode 100644 src/renderer/components/ft-refresh-widget/ft-refresh-widget.vue diff --git a/src/renderer/App.css b/src/renderer/App.css index 24b765ca8..036435314 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -18,10 +18,14 @@ .banner { inline-size: 85%; - margin-block: 20px; + margin-block: 40px 0; margin-inline: auto; } +.banner + .banner { + margin-block: 20px; +} + .banner-wrapper { margin-block: 0; margin-inline: 10px; @@ -53,8 +57,8 @@ } .banner { - inline-size: 80%; - margin-block-start: 20px; + inline-size: 90%; + margin-block: 60px 0; } .flexBox { diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.js b/src/renderer/components/ft-icon-button/ft-icon-button.js index 97062b495..cba8cfb34 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.js +++ b/src/renderer/components/ft-icon-button/ft-icon-button.js @@ -16,6 +16,10 @@ export default defineComponent({ type: Array, default: () => ['fas', 'ellipsis-v'] }, + disabled: { + type: Boolean, + default: false + }, theme: { type: String, default: 'base' @@ -88,6 +92,7 @@ export default defineComponent({ }, handleIconClick: function () { + if (this.disabled) { return } if (this.forceDropdown || (this.dropdownOptions.length > 0)) { this.dropdownShown = !this.dropdownShown @@ -104,6 +109,7 @@ export default defineComponent({ }, handleIconMouseDown: function () { + if (this.disabled) { return } if (this.dropdownShown) { this.mouseDownOnIcon = true } diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.scss b/src/renderer/components/ft-icon-button/ft-icon-button.scss index c76e9333f..6e4f3acb2 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.scss +++ b/src/renderer/components/ft-icon-button/ft-icon-button.scss @@ -79,6 +79,12 @@ } } +.disabled { + opacity: 0.5; + pointer-events: none; + user-select: none; +} + .iconDropdown { background-color: var(--side-nav-color); box-shadow: 0 1px 2px rgb(0 0 0 / 50%); diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.vue b/src/renderer/components/ft-icon-button/ft-icon-button.vue index afc4c68f4..8f4d2bfc7 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.vue +++ b/src/renderer/components/ft-icon-button/ft-icon-button.vue @@ -7,7 +7,8 @@ :icon="icon" :class="{ [theme]: true, - shadow: useShadow + shadow: useShadow, + disabled }" :style="{ padding: padding + 'px', diff --git a/src/renderer/components/ft-list-video/ft-list-video.js b/src/renderer/components/ft-list-video/ft-list-video.js index becb7185d..8d4d6e3bf 100644 --- a/src/renderer/components/ft-list-video/ft-list-video.js +++ b/src/renderer/components/ft-list-video/ft-list-video.js @@ -5,6 +5,7 @@ import { copyToClipboard, formatDurationAsTimestamp, formatNumber, + getRelativeTimeFromDate, openExternalLink, showToast, toDistractionFreeTitle, @@ -345,6 +346,10 @@ export default defineComponent({ return this.historyEntryExists && !this.inHistory }, + currentLocale: function () { + return this.$i18n.locale.replace('_', '-') + }, + externalPlayer: function () { return this.$store.getters.getExternalPlayer }, @@ -462,14 +467,6 @@ export default defineComponent({ return query }, - currentLocale: function () { - return this.$i18n.locale.replace('_', '-') - }, - - showAddToPlaylistPrompt: function () { - return this.$store.getters.getShowAddToPlaylistPrompt - }, - useDeArrowTitles: function () { return this.$store.getters.getUseDeArrowTitles }, @@ -668,48 +665,8 @@ export default defineComponent({ if (this.inHistory) { this.uploadedTime = new Date(this.data.published).toLocaleDateString([this.currentLocale, 'en']) } else { - const now = new Date().getTime() - // Convert from ms to second - // For easier code interpretation the value is made to be positive - let timeDiffFromNow = ((now - this.data.published) / 1000) - let timeUnit = 'second' - - if (timeDiffFromNow >= 60) { - timeDiffFromNow /= 60 - timeUnit = 'minute' - } - - if (timeUnit === 'minute' && timeDiffFromNow >= 60) { - timeDiffFromNow /= 60 - timeUnit = 'hour' - } - - if (timeUnit === 'hour' && timeDiffFromNow >= 24) { - timeDiffFromNow /= 24 - timeUnit = 'day' - } - - const timeDiffFromNowDays = timeDiffFromNow - - if (timeUnit === 'day' && timeDiffFromNow >= 7) { - timeDiffFromNow /= 7 - timeUnit = 'week' - } - // Use 30 days per month, just like calculatePublishedDate - if (timeUnit === 'week' && timeDiffFromNowDays >= 30) { - timeDiffFromNow = timeDiffFromNowDays / 30 - timeUnit = 'month' - } - - if (timeUnit === 'month' && timeDiffFromNow >= 12) { - timeDiffFromNow /= 12 - timeUnit = 'year' - } - - // Using `Math.ceil` so that -1.x days ago displayed as 1 day ago - // Notice that the value is turned to negative to be displayed as "ago" - this.uploadedTime = new Intl.RelativeTimeFormat([this.currentLocale, 'en']).format(Math.ceil(-timeDiffFromNow), timeUnit) + this.uploadedTime = getRelativeTimeFromDate(new Date(this.data.published).toDateString(), false) } } diff --git a/src/renderer/components/ft-notification-banner/ft-notification-banner.css b/src/renderer/components/ft-notification-banner/ft-notification-banner.css index 2f8464be3..6efe9d4f9 100644 --- a/src/renderer/components/ft-notification-banner/ft-notification-banner.css +++ b/src/renderer/components/ft-notification-banner/ft-notification-banner.css @@ -30,3 +30,11 @@ inset-inline-end: 10px; cursor: pointer; } + +@media only screen and (width <= 680px) { + .bannerIcon { + inset-block-start: 27%; + block-size: 25px; + inline-size: 25px; + } +} diff --git a/src/renderer/components/ft-notification-banner/ft-notification-banner.vue b/src/renderer/components/ft-notification-banner/ft-notification-banner.vue index 02feed760..4b3ff2eb0 100644 --- a/src/renderer/components/ft-notification-banner/ft-notification-banner.vue +++ b/src/renderer/components/ft-notification-banner/ft-notification-banner.vue @@ -24,7 +24,7 @@ tabindex="0" :title="$t('Close Banner')" @click.stop="handleClose" - @keydown.enter.stop.prevent="handleClose" + @keydown.enter.space.stop.prevent="handleClose" /> diff --git a/src/renderer/components/ft-refresh-widget/ft-refresh-widget.css b/src/renderer/components/ft-refresh-widget/ft-refresh-widget.css new file mode 100644 index 000000000..3c9b49909 --- /dev/null +++ b/src/renderer/components/ft-refresh-widget/ft-refresh-widget.css @@ -0,0 +1,36 @@ +.floatingRefreshSection { + position: fixed; + inset-block-start: 60px; + inset-inline-end: 0; + box-sizing: border-box; + inline-size: calc(100% - 80px); + padding-block: 5px; + padding-inline: 10px; + box-shadow: 0 2px 1px 0 var(--primary-shadow-color); + background-color: var(--card-bg-color); + border-inline-start: 2px solid var(--primary-color); + display: flex; + align-items: center; + gap: 5px; + justify-content: flex-end; +} + +.floatingRefreshSection:has(.lastRefreshTimestamp + .refreshButton) { + justify-content: space-between; +} + +.floatingRefreshSection.sideNavOpen { + inline-size: calc(100% - 200px); +} + +.lastRefreshTimestamp { + margin-block: 0; + text-align: center; + font-size: 16px; +} + +@media only screen and (width <= 680px) { + .floatingRefreshSection, .floatingRefreshSection.sideNavOpen { + inline-size: 100%; + } +} diff --git a/src/renderer/components/ft-refresh-widget/ft-refresh-widget.js b/src/renderer/components/ft-refresh-widget/ft-refresh-widget.js new file mode 100644 index 000000000..24ca4e1f8 --- /dev/null +++ b/src/renderer/components/ft-refresh-widget/ft-refresh-widget.js @@ -0,0 +1,29 @@ +import { defineComponent } from 'vue' + +import FtIconButton from '../ft-icon-button/ft-icon-button.vue' + +export default defineComponent({ + name: 'FtRefreshWidget', + components: { + 'ft-icon-button': FtIconButton, + }, + props: { + disableRefresh: { + type: Boolean, + default: false + }, + lastRefreshTimestamp: { + type: String, + default: '' + }, + title: { + type: String, + required: true + } + }, + computed: { + isSideNavOpen: function () { + return this.$store.getters.getIsSideNavOpen + } + } +}) diff --git a/src/renderer/components/ft-refresh-widget/ft-refresh-widget.vue b/src/renderer/components/ft-refresh-widget/ft-refresh-widget.vue new file mode 100644 index 000000000..564f1cae8 --- /dev/null +++ b/src/renderer/components/ft-refresh-widget/ft-refresh-widget.vue @@ -0,0 +1,27 @@ + + +