Add ability to import / export playlists and slightly tweak settings layout
This commit is contained in:
parent
907679f440
commit
d21a7f1c24
|
@ -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([
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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%
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue