Implement History and jump to last watched time progress

This commit is contained in:
Preston 2020-08-19 22:39:44 -04:00
parent 6e5e1e0542
commit 2133a10efa
13 changed files with 357 additions and 14 deletions

View File

@ -31,6 +31,7 @@ export default Vue.extend({
},
mounted: function () {
this.$store.dispatch('grabUserSettings')
this.$store.dispatch('grabHistory')
this.$store.commit('setUsingElectron', useElectron)
this.checkThemeSettings()
this.checkLocale()

View File

@ -37,11 +37,13 @@ export default Vue.extend({
duration: '',
description: '',
watched: false,
progressPercentage: 0,
watchProgress: 0,
publishedText: '',
isLive: false,
isFavorited: false,
hideViews: false,
optionsValues: [
'history',
'openYoutube',
'copyYoutube',
'openYoutubeEmbed',
@ -56,6 +58,10 @@ export default Vue.extend({
return this.$store.getters.getUsingElectron
},
historyCache: function () {
return this.$store.getters.getHistoryCache
},
listType: function () {
return this.$store.getters.getListType
},
@ -72,6 +78,12 @@ export default Vue.extend({
return this.$store.getters.getInvidiousInstance
},
inHistory: function () {
// When in the history page, showing relative dates isn't very useful.
// We want to show the exact date instead
return this.$router.currentRoute.name === 'history'
},
invidiousUrl: function () {
return `${this.invidiousInstance}/watch?v=${this.id}`
},
@ -84,8 +96,12 @@ export default Vue.extend({
return `https://www.youtube-nocookie.com/embed/${this.id}`
},
progressPercentage: function () {
return (this.watchProgress / this.data.lengthSeconds) * 100
},
optionsNames: function () {
return [
const names = [
this.$t('Video.Open in YouTube'),
this.$t('Video.Copy YouTube Link'),
this.$t('Video.Open YouTube Embedded Player'),
@ -93,6 +109,14 @@ export default Vue.extend({
this.$t('Video.Open in Invidious'),
this.$t('Video.Copy Invidious Link')
]
if (this.watched) {
names.unshift(this.$t('Video.Remove From History'))
} else {
names.unshift(this.$t('Video.Mark As Watched'))
}
return names
},
thumbnail: function () {
@ -128,6 +152,8 @@ export default Vue.extend({
} else {
this.parseLocalData()
}
this.checkIfWatched()
},
methods: {
toggleSave: function () {
@ -139,6 +165,13 @@ export default Vue.extend({
console.log(option)
switch (option) {
case 'history':
if (this.watched) {
this.removeFromWatched()
} else {
this.markAsWatched()
}
break
case 'copyYoutube':
navigator.clipboard.writeText(this.youtubeUrl)
break
@ -213,7 +246,7 @@ export default Vue.extend({
this.isLive = this.data.liveNow
this.viewCount = this.data.viewCount
if (typeof (this.data.publishedText) !== 'undefined') {
if (typeof (this.data.publishedText) !== 'undefined' && !this.isLive) {
// produces a string according to the template in the locales string
this.toLocalePublicationString({
publishText: this.data.publishedText,
@ -221,7 +254,7 @@ export default Vue.extend({
timeStrings: this.$t('Video.Published'),
liveStreamString: this.$t('Video.Watching'),
upcomingString: this.$t('Video.Published.Upcoming'),
isLive: this.data.live,
isLive: this.isLive,
isUpcoming: this.data.isUpcoming
}).then((data) => {
this.uploadedTime = data
@ -253,7 +286,7 @@ export default Vue.extend({
this.channelId = this.data.ucid
this.viewCount = this.data.views
// Data is returned as a literal string names 'undefined'
// Data is returned as a literal string named 'undefined'
if (this.data.length_seconds !== 'undefined') {
this.duration = this.calculateVideoDuration(parseInt(this.data.length_seconds))
}
@ -265,7 +298,7 @@ export default Vue.extend({
this.channelId = this.channelId.replace('https://www.youtube.com/channel/', '')
}
if (typeof (this.data.uploaded_at) !== 'undefined') {
if (typeof (this.data.uploaded_at) !== 'undefined' && !this.data.live) {
this.toLocalePublicationString({
publishText: this.data.uploaded_at,
templateString: this.$t('Video.Publicationtemplate'),
@ -293,8 +326,67 @@ export default Vue.extend({
this.isLive = this.data.live
},
checkIfWatched: function () {
const historyIndex = this.historyCache.findIndex((video) => {
return video.videoId === this.id
})
if (historyIndex !== -1) {
this.watched = true
this.watchProgress = this.historyCache[historyIndex].watchProgress
if (this.historyCache[historyIndex].published !== '') {
const videoPublished = this.historyCache[historyIndex].published
const videoPublishedDate = new Date(videoPublished)
this.publishedText = videoPublishedDate.toLocaleDateString()
} else {
this.publishedText = ''
}
}
},
markAsWatched: function () {
const videoData = {
videoId: this.id,
title: this.title,
author: this.channelName,
authorId: this.channelId,
published: '',
description: this.description,
viewCount: this.viewCount,
lengthSeconds: this.data.lengthSeconds,
watchProgress: 0,
timeWatched: new Date().getTime(),
isLive: false,
paid: false,
type: 'video'
}
this.updateHistory(videoData)
this.showToast({
message: this.$t('Video.Video has been marked as watched')
})
this.watched = true
},
removeFromWatched: function () {
this.removeFromHistory(this.id)
this.showToast({
message: this.$t('Video.Video has been removed from your history')
})
this.watched = false
},
...mapActions([
'toLocalePublicationString'
'showToast',
'toLocalePublicationString',
'updateHistory',
'removeFromHistory'
])
}
})

View File

@ -84,9 +84,13 @@
<span v-if="viewCount === 1">{{ $t("Video.View") }}</span>
<span v-else-if="parsedViewCount !== ''">{{ $t("Video.Views").toLowerCase() }}</span>
<span
v-if="uploadedTime !== '' && !isLive"
v-if="uploadedTime !== '' && !isLive && !inHistory"
class="uploadedTime"
> {{ uploadedTime }}</span>
<span
v-if="inHistory"
class="uploadedTime"
> {{ publishedText }}</span>
<span
v-if="isLive"
class="viewCount"

View File

@ -257,6 +257,10 @@ export default Vue.extend({
const v = this
this.player.on('ready', function () {
v.$emit('ready')
})
this.player.on('ended', function () {
v.$emit('ended')
})

View File

@ -10,7 +10,6 @@
<div class="switchColumnGrid">
<div class="switchColumn">
<ft-toggle-switch
v-if="false"
label="Remember History"
:compact="true"
:default-value="rememberHistory"

View File

@ -58,6 +58,7 @@ const router = new Router({
},
{
path: '/history',
name: 'history',
meta: {
title: 'History',
icon: 'fa-home'

View File

@ -38,6 +38,14 @@ $thumbnail-overlay-opacity: 0.85
width: 163px
height: auto
.videoWatched
position: absolute
padding: 2px
opacity: $thumbnail-overlay-opacity
color: var(--primary-text-color)
background-color: var(--card-bg-color)
pointer-events: none
.videoDuration
position: absolute
bottom: 4px
@ -66,6 +74,13 @@ $thumbnail-overlay-opacity: 0.85
font-size: 17px
opacity: $thumbnail-overlay-opacity
.watchedProgressBar
height: 2px
position: absolute
bottom: 0px
background-color: var(--primary-color)
z-index: 2
.videoCountContainer
position: absolute
right: 0

View File

@ -0,0 +1,86 @@
import Datastore from 'nedb'
let dbLocation
if (window && window.process && window.process.type === 'renderer') {
// Electron is being used
/* let dbLocation = localStorage.getItem('dbLocation')
if (dbLocation === null) {
const electron = require('electron')
dbLocation = electron.remote.app.getPath('userData')
} */
const electron = require('electron')
dbLocation = electron.remote.app.getPath('userData')
dbLocation = dbLocation + '/history.db'
} else {
dbLocation = 'history.db'
}
const historyDb = new Datastore({
filename: dbLocation,
autoload: true
})
const state = {
historyCache: []
}
const getters = {
getHistoryCache: () => {
return state.historyCache
}
}
const actions = {
grabHistory ({ commit }) {
historyDb.find({}).sort({
timeWatched: -1
}).exec((err, results) => {
if (err) {
console.log(err)
return
}
commit('setHistoryCache', results)
})
},
updateHistory ({ dispatch }, videoData) {
historyDb.update({ videoId: videoData.videoId }, videoData, { upsert: true }, (err, numReplaced) => {
if (!err) {
dispatch('grabHistory')
}
})
},
removeFromHistory ({ dispatch }, videoId) {
historyDb.remove({ videoId: videoId }, (err, numReplaced) => {
if (!err) {
dispatch('grabHistory')
}
})
},
updateWatchProgress ({ dispatch }, videoData) {
historyDb.update({ videoId: videoData.videoId }, { $set: { watchProgress: videoData.watchProgress } }, { upsert: true }, (err, numReplaced) => {
if (!err) {
dispatch('grabHistory')
}
})
}
}
const mutations = {
setHistoryCache (state, historyCache) {
state.historyCache = historyCache
}
}
export default {
state,
getters,
actions,
mutations
}

View File

@ -1,15 +1,59 @@
import Vue from 'vue'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtButton from '../../components/ft-button/ft-button.vue'
export default Vue.extend({
name: 'History',
components: {
'ft-loader': FtLoader,
'ft-card': FtCard,
'ft-flex-box': FtFlexBox,
'ft-element-list': FtElementList
'ft-element-list': FtElementList,
'ft-button': FtButton
},
data: function () {
return {
isLoading: false,
dataLimit: 100
}
},
computed: {
historyCache: function () {
return this.$store.getters.getHistoryCache
},
activeData: function () {
if (this.historyCache.length < this.dataLimit) {
return this.historyCache
} else {
return this.historyCache.slice(0, this.dataLimit)
}
}
},
watch: {
historyCache() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 100)
}
},
mounted: function () {
console.log(this.historyCache)
const limit = sessionStorage.getItem('historyLimit')
if (limit !== null) {
this.dataLimit = limit
}
},
methods: {
increaseLimit: function () {
this.dataLimit += 100
sessionStorage.setItem('historyLimit', this.dataLimit)
}
}
})

View File

@ -1,12 +1,35 @@
<template>
<div>
<ft-card class="card">
<ft-loader
v-if="isLoading"
:fullscreen="true"
/>
<ft-card
v-else
class="card"
>
<h3>{{ $t("History.History") }}</h3>
<ft-flex-box>
<ft-flex-box
v-if="activeData.length === 0"
>
<p class="message">
{{ $t("This part of the app is not ready yet. Come back later when progress has been made.") }}
{{ $t("History['Your history list is currently empty.']") }}
</p>
</ft-flex-box>
<ft-element-list
v-else
:data="activeData"
/>
<ft-flex-box
v-if="activeData.length < historyCache.length"
>
<ft-button
label="Load More"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="increaseLimit"
/>
</ft-flex-box>
</ft-card>
</div>
</template>

View File

@ -47,6 +47,7 @@ export default Vue.extend({
videoViewCount: 0,
videoLikeCount: 0,
videoDislikeCount: 0,
videoLengthSeconds: 0,
channelName: '',
channelThumbnail: '',
channelId: '',
@ -72,6 +73,14 @@ export default Vue.extend({
return this.$store.getters.getUsingElectron
},
historyCache: function () {
return this.$store.getters.getHistoryCache
},
rememberHistory: function () {
return this.$store.getters.getRememberHistory
},
backendPreference: function () {
return this.$store.getters.getBackendPreference
},
@ -276,6 +285,7 @@ export default Vue.extend({
this.activeSourceList = this.videoSourceList
}
} else {
this.videoLengthSeconds = parseInt(result.videoDetails.lengthSeconds)
this.videoSourceList = result.player_response.streamingData.formats
this.audioSourceList = result.player_response.streamingData.adaptiveFormats.filter((format) => {
@ -395,6 +405,7 @@ export default Vue.extend({
} else if (this.forceLocalBackendForLegacy) {
this.getLegacyFormats()
} else {
this.videoLengthSeconds = result.lengthSeconds
this.videoSourceList = result.formatStreams.reverse()
this.audioSourceList = result.adaptiveFormats.filter((format) => {
@ -441,6 +452,46 @@ export default Vue.extend({
})
},
addToHistory: function (watchProgress) {
const videoData = {
videoId: this.videoId,
title: this.videoTitle,
author: this.channelName,
authorId: this.channelId,
published: this.videoPublished,
description: this.videoDescription,
viewCount: this.videoViewCount,
lengthSeconds: this.videoLengthSeconds,
watchProgress: watchProgress,
timeWatched: new Date().getTime(),
isLive: false,
paid: false,
type: 'video'
}
this.updateHistory(videoData)
},
checkIfWatched: function () {
const historyIndex = this.historyCache.findIndex((video) => {
return video.videoId === this.videoId
})
console.log(historyIndex)
if (historyIndex !== -1 && !this.isLive) {
console.log(this.historyCache[historyIndex])
const watchProgress = this.historyCache[historyIndex].watchProgress
this.$refs.videoPlayer.player.currentTime(watchProgress)
}
if (this.rememberHistory && historyIndex !== -1) {
this.addToHistory(this.historyCache[historyIndex].watchProgress)
} else if (this.rememberHistory) {
this.addToHistory(0)
}
},
checkIfPlaylist: function () {
if (typeof (this.$route.query) !== 'undefined') {
this.playlistId = this.$route.query.playlistId
@ -630,7 +681,24 @@ export default Vue.extend({
...mapActions([
'showToast',
'buildVTTFileLocally'
'buildVTTFileLocally',
'updateHistory',
'updateWatchProgress'
])
},
beforeRouteLeave: function (to, from, next) {
if (this.rememberHistory) {
const currentTime = this.$refs.videoPlayer.player.currentTime()
console.log(currentTime)
const payload = {
videoId: this.videoId,
watchProgress: currentTime
}
console.log('update watch progress')
this.updateWatchProgress(payload)
}
next()
}
})

View File

@ -23,6 +23,7 @@
:thumbnail="thumbnail"
class="videoPlayer"
:class="{ theatrePlayer: useTheatreMode }"
@ready="checkIfWatched"
@ended="handleVideoEnded"
@error="handleVideoError"
/>

View File

@ -95,6 +95,7 @@ History:
# On History Page
History: History
Watch History: Watch History
Your history list is currently empty.: Your history list is currently empty.
Settings:
# On Settings Page
Settings: Settings
@ -264,6 +265,10 @@ Channel:
Channel Description: Channel Description
Featured Channels: Featured Channels
Video:
Mark As Watched: Mark As Watched
Remove From History: Remove From History
Video has been marked as watched: Video has been marked as watched
Video has been removed from your history: Video has been removed from your history
Open in YouTube: Open in YouTube
Copy YouTube Link: Copy YouTube Link
Open YouTube Embedded Player: Open YouTube Embedded Player