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 () {
return this.$store.getters.getProfileList
},
allPlaylists: function () {
return this.$store.getters.getAllPlaylists
},
importSubscriptionsPromptNames: function () {
const importFreeTube = this.$t('Settings.Data Settings.Import FreeTube')
const importYouTube = this.$t('Settings.Data Settings.Import YouTube')
@ -245,7 +248,6 @@ export default Vue.extend({
return
}
const textDecode = new TextDecoder('utf-8').decode(data)
console.log(textDecode)
const youtubeSubscriptions = textDecode.split('\n')
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = []
@ -317,8 +319,6 @@ export default Vue.extend({
let textDecode = new TextDecoder('utf-8').decode(data)
textDecode = JSON.parse(textDecode)
console.log(textDecode)
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
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) {
const convertedData = []
for (const channel of oldData) {
@ -1178,7 +1340,9 @@ export default Vue.extend({
'calculateColorLuminance',
'showOpenDialog',
'showSaveDialog',
'getUserDataPath'
'getUserDataPath',
'addPlaylist',
'addVideo'
]),
...mapMutations([

View File

@ -44,6 +44,16 @@
@click="openProfileSettings"
/>
</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
v-if="showImportSubscriptionsPrompt"
:label="$t('Settings.Data Settings.Select Import Type')"

View File

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

View File

@ -21,6 +21,9 @@ details
width: 85%
margin: 0 auto
&[open]
padding-bottom: 15px
hr
width: 100%
height: 2px
@ -32,7 +35,7 @@ details
display: block
cursor: pointer
padding: 1px 1px 1px 1px
h3
margin-left: 2%

View File

@ -282,6 +282,8 @@ Settings:
Export NewPipe: Export NewPipe
Import History: Import History
Export History: Export History
Import Playlists: Import Playlists
Export Playlists: Export Playlists
Profile object has insufficient data, skipping item: Profile object has insufficient
data, skipping item
All subscriptions and profiles have been successfully imported: All subscriptions
@ -301,6 +303,11 @@ Settings:
successfully imported
All watched history has been successfully exported: All watched history has been
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 write file: Unable to write file
Unknown data key: Unknown data key