diff --git a/package.json b/package.json index 8205a1f09..66d866642 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-brands-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", "@fortawesome/vue-fontawesome": "^2.0.10", "@seald-io/nedb": "^4.0.4", "@silvermine/videojs-quality-selector": "^1.3.1", 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 cba8cfb34..90ca0fb20 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.js +++ b/src/renderer/components/ft-icon-button/ft-icon-button.js @@ -65,7 +65,7 @@ export default defineComponent({ default: false } }, - emits: ['click'], + emits: ['click', 'disabled-click'], data: function () { return { dropdownShown: false, @@ -92,7 +92,10 @@ export default defineComponent({ }, handleIconClick: function () { - if (this.disabled) { return } + if (this.disabled) { + this.$emit('disabled-click') + return + } if (this.forceDropdown || (this.dropdownOptions.length > 0)) { this.dropdownShown = !this.dropdownShown 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 0e35323f4..d4f0eca36 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.scss +++ b/src/renderer/components/ft-icon-button/ft-icon-button.scss @@ -21,19 +21,21 @@ background-color: var(--card-bg-color); color: var(--primary-text-color); - &:hover, - &:focus-visible { - background-color: var(--side-nav-hover-color); - color: var(--side-nav-hover-text-color); - } + &:not(.disabled) { + &:hover, + &:focus-visible { + background-color: var(--side-nav-hover-color); + color: var(--side-nav-hover-text-color); + } - &:active { - background-color: var(--side-nav-active-color); - color: var(--side-nav-active-text-color); + &:active { + background-color: var(--side-nav-active-color); + color: var(--side-nav-active-text-color); + } } } - &.base-no-default { + &.base-no-default:not(.disabled) { &:hover, &:focus-visible { background-color: var(--side-nav-hover-color); @@ -50,27 +52,32 @@ background-color: var(--primary-color); color: var(--text-with-main-color); - &:hover, - &:focus-visible { - background-color: var(--primary-color-hover); - } + &:not(.disabled) { + &:hover, + &:focus-visible { + background-color: var(--primary-color-hover); + } - &:active { - background-color: var(--primary-color-active); + &:active { + background-color: var(--primary-color-active); + } } } + &.secondary { background-color: var(--accent-color); color: var(--text-with-accent-color); - &:hover, - &:focus-visible { - background-color: var(--accent-color-hover); - } + &:not(.disabled) { + &:hover, + &:focus-visible { + background-color: var(--accent-color-hover); + } - &:active { - background-color: var(--accent-color-active); + &:active { + background-color: var(--accent-color-active); + } } } @@ -78,13 +85,15 @@ background-color: var(--destructive-color); color: var(--destructive-text-color); - &:hover, - &:focus-visible { - background-color: var(--destructive-hover-color); - } + &:not(.disabled) { + &:hover, + &:focus-visible { + background-color: var(--destructive-hover-color); + } - &:active { - background-color: var(--destructive-active-color); + &:active { + background-color: var(--destructive-active-color); + } } } @@ -95,7 +104,8 @@ .disabled { opacity: 0.5; - pointer-events: none; + pointer-events: auto; + cursor: default; user-select: none; } 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 8f4d2bfc7..4c6af4f0e 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.vue +++ b/src/renderer/components/ft-icon-button/ft-icon-button.vue @@ -16,6 +16,7 @@ }" tabindex="0" role="button" + :aria-disabled="disabled" :aria-expanded="dropdownShown" @click="handleIconClick" @mousedown="handleIconMouseDown" diff --git a/src/renderer/components/ft-list-playlist/ft-list-playlist.js b/src/renderer/components/ft-list-playlist/ft-list-playlist.js index 2ac0f5baf..41c03aed7 100644 --- a/src/renderer/components/ft-list-playlist/ft-list-playlist.js +++ b/src/renderer/components/ft-list-playlist/ft-list-playlist.js @@ -1,6 +1,7 @@ import { defineComponent } from 'vue' import FtIconButton from '../ft-icon-button/ft-icon-button.vue' import { mapActions } from 'vuex' +import { showToast } from '../../helpers/utils' export default defineComponent({ name: 'FtListPlaylist', @@ -40,6 +41,20 @@ export default defineComponent({ return this.$store.getters.getCurrentInvidiousInstance }, + quickBookmarkPlaylistId() { + return this.$store.getters.getQuickBookmarkTargetPlaylistId + }, + quickBookmarkPlaylist() { + return this.$store.getters.getPlaylist(this.quickBookmarkPlaylistId) + }, + markedAsQuickBookmarkTarget() { + // Only user playlists can be target + if (this.playlistId == null) { return false } + if (this.quickBookmarkPlaylistId == null) { return false } + + return this.quickBookmarkPlaylistId === this.playlistId + }, + listType: function () { return this.$store.getters.getListType }, @@ -112,6 +127,10 @@ export default defineComponent({ }) }, + handleQuickBookmarkEnabledDisabledClick: function () { + showToast(this.$t('User Playlists.SinglePlaylistView.Toast["This playlist is already being used for quick bookmark."]')) + }, + parseInvidiousData: function () { this.title = this.data.title if (this.thumbnailCanBeShown) { @@ -154,8 +173,34 @@ export default defineComponent({ this.videoCount = this.data.videos.length }, + enableQuickBookmarkForThisPlaylist: function () { + const currentQuickBookmarkTargetPlaylist = this.quickBookmarkPlaylist + + this.updateQuickBookmarkTargetPlaylistId(this.playlistId) + if (currentQuickBookmarkTargetPlaylist != null) { + showToast( + this.$t('User Playlists.SinglePlaylistView.Toast["This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo"]', { + oldPlaylistName: currentQuickBookmarkTargetPlaylist.playlistName, + }), + 5000, + () => { + this.updateQuickBookmarkTargetPlaylistId(currentQuickBookmarkTargetPlaylist._id) + showToast( + this.$t('User Playlists.SinglePlaylistView.Toast["Reverted to use {oldPlaylistName} for quick bookmark"]', { + oldPlaylistName: currentQuickBookmarkTargetPlaylist.playlistName, + }), + 5000, + ) + }, + ) + } else { + showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist is now used for quick bookmark')) + } + }, + ...mapActions([ - 'openInExternalPlayer' + 'openInExternalPlayer', + 'updateQuickBookmarkTargetPlaylistId' ]) } }) diff --git a/src/renderer/components/ft-list-playlist/ft-list-playlist.vue b/src/renderer/components/ft-list-playlist/ft-list-playlist.vue index 503c800c6..0f5bed277 100644 --- a/src/renderer/components/ft-list-playlist/ft-list-playlist.vue +++ b/src/renderer/components/ft-list-playlist/ft-list-playlist.vue @@ -67,6 +67,20 @@ :use-shadow="false" @click="handleExternalPlayer" /> + + + diff --git a/src/renderer/components/playlist-info/playlist-info.js b/src/renderer/components/playlist-info/playlist-info.js index d5c9ca772..3afaeea48 100644 --- a/src/renderer/components/playlist-info/playlist-info.js +++ b/src/renderer/components/playlist-info/playlist-info.js @@ -230,9 +230,6 @@ export default defineComponent({ quickBookmarkPlaylist() { return this.$store.getters.getPlaylist(this.quickBookmarkPlaylistId) }, - quickBookmarkEnabled() { - return this.quickBookmarkPlaylist != null - }, markedAsQuickBookmarkTarget() { // Only user playlists can be target if (this.selectedUserPlaylist == null) { return false } @@ -240,6 +237,9 @@ export default defineComponent({ return this.quickBookmarkPlaylist._id === this.selectedUserPlaylist._id }, + playlistDeletionDisabledLabel: function () { + return this.$t('User Playlists["Cannot delete the quick bookmark target playlist."]') + }, }, watch: { showDeletePlaylistPrompt(shown) { @@ -319,6 +319,14 @@ export default defineComponent({ }) }, + handleQuickBookmarkEnabledDisabledClick: function () { + showToast(this.$t('User Playlists.SinglePlaylistView.Toast["This playlist is already being used for quick bookmark."]')) + }, + + handlePlaylistDeleteDisabledClick: function () { + showToast(this.playlistDeletionDisabledLabel) + }, + exitEditMode: function () { this.editMode = false @@ -402,10 +410,6 @@ export default defineComponent({ showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist is now used for quick bookmark')) } }, - disableQuickBookmark() { - this.updateQuickBookmarkTargetPlaylistId(null) - showToast(this.$t('User Playlists.SinglePlaylistView.Toast.Quick bookmark disabled')) - }, updateQuery(query) { this.query = query diff --git a/src/renderer/components/playlist-info/playlist-info.vue b/src/renderer/components/playlist-info/playlist-info.vue index b2d35e849..384882041 100644 --- a/src/renderer/components/playlist-info/playlist-info.vue +++ b/src/renderer/components/playlist-info/playlist-info.vue @@ -122,7 +122,15 @@ theme="secondary" @click="exitEditMode" /> - + - - playlist.videos.length === 0) + if (emptyPlaylist) return emptyPlaylist + + let max = -1 + let maxIndex = 0 + for (let i = 0; i < playlists.length; i++) { + if (playlists[i].lastPlayedAt != null && playlists[i].lastPlayedAt > max) { + maxIndex = i + max = playlists[i].lastPlayedAt + } + } + + return playlists[maxIndex] +} + const state = { // Playlist loading takes time on app load (new windows) // This is necessary to let components to know when to start data loading @@ -46,7 +66,7 @@ const getters = { } const actions = { - async addPlaylist({ commit }, payload) { + async addPlaylist({ state, commit, rootState, dispatch }, payload) { // In case internal id is forgotten, generate one (instead of relying on caller and have a chance to cause data corruption) if (payload._id == null) { // {Time now in unix time}-{0-9999} @@ -79,15 +99,28 @@ const actions = { try { await DBPlaylistHandlers.create([payload]) + + const noQuickBookmarkSet = !rootState.settings.quickBookmarkTargetPlaylistId || !state.playlists.some((playlist) => playlist._id === rootState.settings.quickBookmarkTargetPlaylistId) + if (noQuickBookmarkSet) { + dispatch('updateQuickBookmarkTargetPlaylistId', payload._id, { root: true }) + } + commit('addPlaylist', payload) } catch (errMessage) { console.error(errMessage) } }, - async addPlaylists({ commit }, payload) { + async addPlaylists({ state, commit, rootState, dispatch }, payload) { try { await DBPlaylistHandlers.create(payload) + + const noQuickBookmarkSet = !rootState.settings.quickBookmarkTargetPlaylistId || !state.playlists.some((playlist) => playlist._id === rootState.settings.quickBookmarkTargetPlaylistId) + if (noQuickBookmarkSet) { + const chosenPlaylist = findEmptyOrLatestPlayedPlaylist(payload) + dispatch('updateQuickBookmarkTargetPlaylistId', chosenPlaylist._id, { root: true }) + } + commit('addPlaylists', payload) } catch (errMessage) { console.error(errMessage) @@ -185,7 +218,7 @@ const actions = { } }, - async grabAllPlaylists({ commit, dispatch, state }) { + async grabAllPlaylists({ rootState, commit, dispatch, state }) { try { const payload = (await DBPlaylistHandlers.find()).filter((e) => e != null) if (payload.length === 0) { @@ -308,6 +341,13 @@ const actions = { } } + // if no quick bookmark is set, try to find another playlist + const noQuickBookmarkSet = !rootState.settings.quickBookmarkTargetPlaylistId || !payload.some((playlist) => playlist._id === rootState.settings.quickBookmarkTargetPlaylistId) + if (noQuickBookmarkSet && payload.length > 0) { + const chosenPlaylist = findEmptyOrLatestPlayedPlaylist(payload) + dispatch('updateQuickBookmarkTargetPlaylistId', chosenPlaylist._id, { root: true }) + } + commit('setAllPlaylists', payload) } commit('setPlaylistsReady', true) diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml index b661fdaa5..b6dce12a5 100644 --- a/static/locales/en-US.yaml +++ b/static/locales/en-US.yaml @@ -169,9 +169,10 @@ User Playlists: Copy Playlist: Copy Playlist Remove Watched Videos: Remove Watched Videos Enable Quick Bookmark With This Playlist: Enable Quick Bookmark With This Playlist - Disable Quick Bookmark: Disable Quick Bookmark + Quick Bookmark Enabled: Quick Bookmark Enabled Are you sure you want to remove all watched videos from this playlist? This cannot be undone: Are you sure you want to remove all watched videos from this playlist? This cannot be undone. Delete Playlist: Delete Playlist + Cannot delete the quick bookmark target playlist.: Cannot delete the quick bookmark target playlist. Are you sure you want to delete this playlist? This cannot be undone: Are you sure you want to delete this playlist? This cannot be undone. Sort By: @@ -197,8 +198,8 @@ User Playlists: Video has been removed: Video has been removed There was a problem with removing this video: There was a problem with removing this video + This playlist is already being used for quick bookmark.: This playlist is already being used for quick bookmark. This playlist is now used for quick bookmark: This playlist is now used for quick bookmark - Quick bookmark disabled: Quick bookmark disabled This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo: This playlist is now used for quick bookmark instead of {oldPlaylistName}. Click here to undo Reverted to use {oldPlaylistName} for quick bookmark: Reverted to use {oldPlaylistName} for quick bookmark @@ -210,6 +211,7 @@ User Playlists: There were no videos to remove.: There were no videos to remove. This playlist is protected and cannot be removed.: This playlist is protected and cannot be removed. Playlist {playlistName} has been deleted.: Playlist {playlistName} has been deleted. + Playlist {playlistName} is the new quick bookmark playlist.: Playlist {playlistName} is the new quick bookmark playlist. This playlist does not exist: This playlist does not exist AddVideoPrompt: diff --git a/yarn.lock b/yarn.lock index f7191c47a..d209ad9cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1316,6 +1316,13 @@ dependencies: "@fortawesome/fontawesome-common-types" "6.5.2" +"@fortawesome/free-regular-svg-icons@^6.5.2": + version "6.5.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz#e8e04b4368d49920abdf1bacc63c67c870635222" + integrity sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw== + dependencies: + "@fortawesome/fontawesome-common-types" "6.5.2" + "@fortawesome/free-solid-svg-icons@^6.5.2": version "6.5.2" resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz#9b40b077b27400a5e9fcbf2d15b986c7be69e9ca" @@ -7921,16 +7928,7 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8016,14 +8014,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9005,16 +8996,7 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==