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==