Add ability to import / export playlists and slightly tweak settings layout

This commit is contained in:
PrestonN 2022-02-06 14:31:27 -05:00
parent 907679f440
commit d21a7f1c24
5 changed files with 206 additions and 20 deletions

View File

@ -54,6 +54,9 @@ export default Vue.extend({
profileList: function () { profileList: function () {
return this.$store.getters.getProfileList return this.$store.getters.getProfileList
}, },
allPlaylists: function () {
return this.$store.getters.getAllPlaylists
},
importSubscriptionsPromptNames: function () { importSubscriptionsPromptNames: function () {
const importFreeTube = this.$t('Settings.Data Settings.Import FreeTube') const importFreeTube = this.$t('Settings.Data Settings.Import FreeTube')
const importYouTube = this.$t('Settings.Data Settings.Import YouTube') const importYouTube = this.$t('Settings.Data Settings.Import YouTube')
@ -245,7 +248,6 @@ export default Vue.extend({
return return
} }
const textDecode = new TextDecoder('utf-8').decode(data) const textDecode = new TextDecoder('utf-8').decode(data)
console.log(textDecode)
const youtubeSubscriptions = textDecode.split('\n') const youtubeSubscriptions = textDecode.split('\n')
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0])) const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = [] const subscriptions = []
@ -317,8 +319,6 @@ export default Vue.extend({
let textDecode = new TextDecoder('utf-8').decode(data) let textDecode = new TextDecoder('utf-8').decode(data)
textDecode = JSON.parse(textDecode) textDecode = JSON.parse(textDecode)
console.log(textDecode)
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0])) const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = [] const subscriptions = []
@ -1075,6 +1075,168 @@ export default Vue.extend({
}) })
}, },
importPlaylists: async function () {
const options = {
properties: ['openFile'],
filters: [
{
name: 'Database File',
extensions: ['db']
}
]
}
const response = await this.showOpenDialog(options)
if (response.canceled || response.filePaths.length === 0) {
return
}
const filePath = response.filePaths[0]
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
}
const playlists = JSON.parse(data)
playlists.forEach(async (playlistData) => {
// We would technically already be done by the time the data is parsed,
// however we want to limit the possibility of malicious data being sent
// to the app, so we'll only grab the data we need here.
const requiredKeys = [
'playlistName',
'videos'
]
const optionalKeys = [
'_id',
'protected',
'removeOnWatched'
]
const requiredVideoKeys = [
'videoId',
'title',
'author',
'authorId',
'published',
'lengthSeconds',
'timeAdded',
'isLive',
'paid',
'type'
]
const playlistObject = {}
Object.keys(playlistData).forEach((key) => {
if (!requiredKeys.includes(key) && !optionalKeys.includes(key)) {
const message = `${this.$t('Settings.Data Settings.Unknown data key')}: ${key}`
this.showToast({
message: message
})
} else if (key === 'videos') {
const videoArray = []
playlistData.videos.forEach((video) => {
let hasAllKeys = true
Object.keys(video).forEach((videoKey) => {
if (!requiredVideoKeys.includes(videoKey)) {
hasAllKeys = false
}
})
if (hasAllKeys) {
videoArray.push(video)
}
})
playlistObject[key] = videoArray
} else {
playlistObject[key] = playlistData[key]
}
})
const objectKeys = Object.keys(playlistObject)
if ((objectKeys.length < requiredKeys.length) || playlistObject.videos.length === 0) {
const message = this.$t('Settings.Data Settings.Playlist insufficient data').replace('$', playlistData.playlistName)
this.showToast({
message: message
})
} else {
const existingPlaylist = this.allPlaylists.find((playlist) => {
return playlist.playlistName === playlistObject.playlistName
})
if (existingPlaylist !== undefined) {
playlistObject.videos.forEach((video) => {
const existingVideo = existingPlaylist.videos.find((x) => {
return x.videoId === video.videoId
})
if (existingVideo === undefined) {
const payload = {
playlistName: existingPlaylist.playlistName,
videoData: video
}
this.addVideo(payload)
}
})
} else {
this.addPlaylist(playlistObject)
}
}
})
this.showToast({
message: this.$t('Settings.Data Settings.All playlists has been successfully imported')
})
})
},
exportPlaylists: async function () {
const date = new Date().toISOString().split('T')[0]
const exportFileName = 'freetube-playlists-' + date + '.db'
const options = {
defaultPath: exportFileName,
filters: [
{
name: 'Database File',
extensions: ['db']
}
]
}
const response = await this.showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
const filePath = response.filePath
fs.writeFile(filePath, JSON.stringify(this.allPlaylists), (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
this.showToast({
message: this.$t('Settings.Data Settings.All playlists has been successfully exported')
})
})
},
async convertOldFreeTubeFormatToNew(oldData) { async convertOldFreeTubeFormatToNew(oldData) {
const convertedData = [] const convertedData = []
for (const channel of oldData) { for (const channel of oldData) {
@ -1178,7 +1340,9 @@ export default Vue.extend({
'calculateColorLuminance', 'calculateColorLuminance',
'showOpenDialog', 'showOpenDialog',
'showSaveDialog', 'showSaveDialog',
'getUserDataPath' 'getUserDataPath',
'addPlaylist',
'addVideo'
]), ]),
...mapMutations([ ...mapMutations([

View File

@ -44,6 +44,16 @@
@click="openProfileSettings" @click="openProfileSettings"
/> />
</ft-flex-box> </ft-flex-box>
<ft-flex-box>
<ft-button
:label="$t('Settings.Data Settings.Import Playlists')"
@click="importPlaylists"
/>
<ft-button
:label="$t('Settings.Data Settings.Export Playlists')"
@click="exportPlaylists"
/>
</ft-flex-box>
<ft-prompt <ft-prompt
v-if="showImportSubscriptionsPrompt" v-if="showImportSubscriptionsPrompt"
:label="$t('Settings.Data Settings.Select Import Type')" :label="$t('Settings.Data Settings.Select Import Type')"

View File

@ -13,10 +13,9 @@
@change="handleDownloadingSettingChange" @change="handleDownloadingSettingChange"
/> />
</ft-flex-box> </ft-flex-box>
<div <ft-flex-box
v-if="!askForDownloadPath" v-if="!askForDownloadPath"
> >
<ft-flex-box>
<ft-input <ft-input
class="folderDisplay" class="folderDisplay"
:placeholder="downloadPath" :placeholder="downloadPath"
@ -24,12 +23,15 @@
:show-label="false" :show-label="false"
:disabled="true" :disabled="true"
/> />
</ft-flex-box>
<ft-flex-box
v-if="!askForDownloadPath"
>
<ft-button <ft-button
:label="$t('Settings.Download Settings.Choose Path')" :label="$t('Settings.Download Settings.Choose Path')"
@click="chooseDownloadingFolder" @click="chooseDownloadingFolder"
/> />
</ft-flex-box> </ft-flex-box>
</div>
</details> </details>
</template> </template>

View File

@ -21,6 +21,9 @@ details
width: 85% width: 85%
margin: 0 auto margin: 0 auto
&[open]
padding-bottom: 15px
hr hr
width: 100% width: 100%
height: 2px height: 2px

View File

@ -282,6 +282,8 @@ Settings:
Export NewPipe: Export NewPipe Export NewPipe: Export NewPipe
Import History: Import History Import History: Import History
Export History: Export History Export History: Export History
Import Playlists: Import Playlists
Export Playlists: Export Playlists
Profile object has insufficient data, skipping item: Profile object has insufficient Profile object has insufficient data, skipping item: Profile object has insufficient
data, skipping item data, skipping item
All subscriptions and profiles have been successfully imported: All subscriptions All subscriptions and profiles have been successfully imported: All subscriptions
@ -301,6 +303,11 @@ Settings:
successfully imported successfully imported
All watched history has been successfully exported: All watched history has been All watched history has been successfully exported: All watched history has been
successfully exported successfully exported
Playlist insufficient data: Insufficient data for "$" playlist, skipping item
All playlists has been successfully imported: All playlists has been
successfully imported
All playlists has been successfully exported: All playlists has been
successfully exported
Unable to read file: Unable to read file Unable to read file: Unable to read file
Unable to write file: Unable to write file Unable to write file: Unable to write file
Unknown data key: Unknown data key Unknown data key: Unknown data key