Always have quick bookmark set (#5058)

* Update disabled ft-icon-button styling to show title on hover and use aria-disabled

* Update Quick Bookmark button logic

* Import regular fontawesome icons for showing the bookmark

The original plan: use the Quick Bookmark icons here for enabled/disabled sates. Problem: colors would be off if we used the same background color for the ft-list-item. Button would barely appear visible. Thereore, we should probably use the existing 'secondary' theme. Problem: the 'checked' icon then looks unrecognizable and instead generic. Solution: use 'unfilled'/regular icon version for disabled state and 'filled'/solid icon version for enabled state, specifically on the ft-playlist-info page.

* Implement Quick Bookmark establishment on startup, playlist addition, and playlist deletion

* Update translation text

Co-authored-by: efb4f5ff-1298-471a-8973-3d47447115dc <73130443+efb4f5ff-1298-471a-8973-3d47447115dc@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>

* Prevent quick bookmark playlist target from being deleted

* Co-authored code with @PikachuEXE allowing quick bookmark to be set on ft-list-playlist item

* Update logic to use empty or latest played playlist as fallback target

Also removes now unneeded on-removal logic.

* Update to add toast messages on disabled icon buttons

* Update disabled bookmark icon styling

* Update to use @disabled-click event instead of showToast-specific behavior

The prior implementation was also leading into module loading errors.

* Update label to be more clear, and add periods to convey more seriousness

* Update casing for label

* Remove outdated piece of code

---------

Co-authored-by: PikachuEXE <git@pikachuexe.net>
Co-authored-by: efb4f5ff-1298-471a-8973-3d47447115dc <73130443+efb4f5ff-1298-471a-8973-3d47447115dc@users.noreply.github.com>
Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>
This commit is contained in:
Jason 2024-05-20 09:53:05 +00:00 committed by GitHub
parent 9151403017
commit f2d98887f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 191 additions and 87 deletions

View File

@ -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",

View File

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

View File

@ -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;
}

View File

@ -16,6 +16,7 @@
}"
tabindex="0"
role="button"
:aria-disabled="disabled"
:aria-expanded="dropdownShown"
@click="handleIconClick"
@mousedown="handleIconMouseDown"

View File

@ -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'
])
}
})

View File

@ -67,6 +67,20 @@
:use-shadow="false"
@click="handleExternalPlayer"
/>
<span
v-if="isUserPlaylist"
class="playlistIcons"
>
<ft-icon-button
:title="markedAsQuickBookmarkTarget ? $t('User Playlists.Quick Bookmark Enabled') : $t('User Playlists.Enable Quick Bookmark With This Playlist')"
:icon="markedAsQuickBookmarkTarget ? ['fas', 'bookmark'] : ['far', 'bookmark']"
:disabled="markedAsQuickBookmarkTarget"
:theme="markedAsQuickBookmarkTarget ? 'secondary' : 'base-no-default'"
:size="16"
@disabled-click="handleQuickBookmarkEnabledDisabledClick"
@click="enableQuickBookmarkForThisPlaylist"
/>
</span>
</div>
</div>
</template>

View File

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

View File

@ -122,7 +122,15 @@
theme="secondary"
@click="exitEditMode"
/>
<ft-icon-button
v-if="!editMode && isUserPlaylist"
:title="markedAsQuickBookmarkTarget ? $t('User Playlists.Quick Bookmark Enabled') : $t('User Playlists.Enable Quick Bookmark With This Playlist')"
:icon="markedAsQuickBookmarkTarget ? ['fas', 'bookmark'] : ['far', 'bookmark']"
:disabled="markedAsQuickBookmarkTarget"
:theme="markedAsQuickBookmarkTarget ? 'secondary' : 'base-no-default'"
@disabled-click="handleQuickBookmarkEnabledDisabledClick"
@click="enableQuickBookmarkForThisPlaylist"
/>
<ft-icon-button
v-if="!editMode && isUserPlaylist"
:title="$t('User Playlists.Edit Playlist Info')"
@ -137,20 +145,6 @@
theme="secondary"
@click="toggleCopyVideosPrompt"
/>
<ft-icon-button
v-if="!editMode && isUserPlaylist && !markedAsQuickBookmarkTarget"
:title="$t('User Playlists.Enable Quick Bookmark With This Playlist')"
:icon="['fas', 'link']"
theme="secondary"
@click="enableQuickBookmarkForThisPlaylist"
/>
<ft-icon-button
v-if="!editMode && isUserPlaylist && markedAsQuickBookmarkTarget"
:title="$t('User Playlists.Disable Quick Bookmark')"
:icon="['fas', 'link-slash']"
theme="secondary"
@click="disableQuickBookmark"
/>
<ft-icon-button
v-if="!editMode && isUserPlaylist && videoCount > 0"
:title="$t('User Playlists.Remove Watched Videos')"
@ -160,9 +154,11 @@
/>
<ft-icon-button
v-if="deletePlaylistButtonVisible"
:title="$t('User Playlists.Delete Playlist')"
:disabled="markedAsQuickBookmarkTarget"
:title="!markedAsQuickBookmarkTarget ? $t('User Playlists.Delete Playlist') : playlistDeletionDisabledLabel"
:icon="['fas', 'trash']"
theme="destructive"
@disabled-click="handlePlaylistDeleteDisabledClick"
@click="showDeletePlaylistPrompt = true"
/>
<ft-share-button

View File

@ -90,6 +90,9 @@ import {
faTrash,
faUsers,
} from '@fortawesome/free-solid-svg-icons'
import {
faBookmark as farBookmark
} from '@fortawesome/free-regular-svg-icons'
import {
faBitcoin,
faGithub,
@ -185,6 +188,9 @@ library.add(
faTrash,
faUsers,
// solid icons
farBookmark,
// brand icons
faGithub,
faBitcoin,

View File

@ -13,6 +13,26 @@ function generateRandomUniqueId() {
return crypto.randomUUID ? crypto.randomUUID() : `id-${Date.now()}-${Math.floor(Math.random() * 10000)}`
}
/*
* Function to find the first playlist with 0 videos, or otherwise the most recently accessed.
* This is a good default quick bookmark target if one needs to be set.
*/
function findEmptyOrLatestPlayedPlaylist(playlists) {
const emptyPlaylist = playlists.find((playlist) => 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)

View File

@ -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:

View File

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