Move `Dialog` functions to `utils/helpers` (#2752)
* Moving `FileFromDialog` helpers into helpers/utils * Moving `showDialog` functions to `utils/helpers` * Linting * Update src/renderer/helpers/utils.js Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> * Update refs `showSaveDialog` in `ft-video-player` * Formatting long import to be multiline Co-authored-by: PikachuEXE <pikachuexe@gmail.com> Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> Co-authored-by: PikachuEXE <pikachuexe@gmail.com>
This commit is contained in:
parent
a236b787e3
commit
70baa873fb
|
@ -10,7 +10,16 @@ import { MAIN_PROFILE_ID } from '../../../constants'
|
|||
|
||||
import { opmlToJSON } from 'opml-to-json'
|
||||
import ytch from 'yt-channel-info'
|
||||
import { calculateColorLuminance, copyToClipboard, getRandomColor, showToast } from '../../helpers/utils'
|
||||
import {
|
||||
calculateColorLuminance,
|
||||
copyToClipboard,
|
||||
getRandomColor,
|
||||
readFileFromDialog,
|
||||
showOpenDialog,
|
||||
showSaveDialog,
|
||||
showToast,
|
||||
writeFileFromDialog
|
||||
} from '../../helpers/utils'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'DataSettings',
|
||||
|
@ -97,13 +106,13 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showOpenDialog(options)
|
||||
const response = await showOpenDialog(options)
|
||||
if (response.canceled || response.filePaths?.length === 0) {
|
||||
return
|
||||
}
|
||||
let textDecode
|
||||
try {
|
||||
textDecode = await this.readFileFromDialog({ response })
|
||||
textDecode = await readFileFromDialog(response)
|
||||
} catch (err) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||
showToast(`${message}: ${err}`)
|
||||
|
@ -503,13 +512,13 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: subscriptionsDb })
|
||||
await writeFileFromDialog(response, subscriptionsDb)
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -568,14 +577,14 @@ export default Vue.extend({
|
|||
return object
|
||||
})
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: JSON.stringify(subscriptionsObject) })
|
||||
await writeFileFromDialog(response, JSON.stringify(subscriptionsObject))
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -613,14 +622,14 @@ export default Vue.extend({
|
|||
}
|
||||
})
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: opmlData })
|
||||
await writeFileFromDialog(response, opmlData)
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -652,14 +661,14 @@ export default Vue.extend({
|
|||
exportText += `${channel.id},${channelUrl},${channelName}\n`
|
||||
})
|
||||
exportText += '\n'
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: exportText })
|
||||
await writeFileFromDialog(response, exportText)
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -699,13 +708,13 @@ export default Vue.extend({
|
|||
newPipeObject.subscriptions.push(subscription)
|
||||
})
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: JSON.stringify(newPipeObject) })
|
||||
await writeFileFromDialog(response, JSON.stringify(newPipeObject))
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -725,13 +734,13 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showOpenDialog(options)
|
||||
const response = await showOpenDialog(options)
|
||||
if (response.canceled || response.filePaths?.length === 0) {
|
||||
return
|
||||
}
|
||||
let textDecode
|
||||
try {
|
||||
textDecode = await this.readFileFromDialog({ response })
|
||||
textDecode = await readFileFromDialog(response)
|
||||
} catch (err) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||
showToast(`${message}: ${err}`)
|
||||
|
@ -799,14 +808,14 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: historyDb })
|
||||
await writeFileFromDialog(response, historyDb)
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -825,13 +834,13 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showOpenDialog(options)
|
||||
const response = await showOpenDialog(options)
|
||||
if (response.canceled || response.filePaths?.length === 0) {
|
||||
return
|
||||
}
|
||||
let data
|
||||
try {
|
||||
data = await this.readFileFromDialog({ response })
|
||||
data = await readFileFromDialog(response)
|
||||
} catch (err) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||
showToast(`${message}: ${err}`)
|
||||
|
@ -942,13 +951,13 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this.writeFileFromDialog({ response, content: JSON.stringify(this.allPlaylists) })
|
||||
await writeFileFromDialog(response, JSON.stringify(this.allPlaylists))
|
||||
} catch (writeErr) {
|
||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||
showToast(`${message}: ${writeErr}`)
|
||||
|
@ -1100,10 +1109,6 @@ export default Vue.extend({
|
|||
'updateShowProgressBar',
|
||||
'updateHistory',
|
||||
'compactHistory',
|
||||
'showOpenDialog',
|
||||
'readFileFromDialog',
|
||||
'showSaveDialog',
|
||||
'writeFileFromDialog',
|
||||
'getUserDataPath',
|
||||
'addPlaylist',
|
||||
'addVideo'
|
||||
|
|
|
@ -15,7 +15,7 @@ import 'videojs-mobile-ui'
|
|||
import 'videojs-mobile-ui/dist/videojs-mobile-ui.css'
|
||||
import { IpcChannels } from '../../../constants'
|
||||
import { sponsorBlockSkipSegments } from '../../helpers/sponsorblock'
|
||||
import { calculateColorLuminance, colors, showToast } from '../../helpers/utils'
|
||||
import { calculateColorLuminance, colors, showSaveDialog, showToast } from '../../helpers/utils'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FtVideoPlayer',
|
||||
|
@ -1396,7 +1396,7 @@ export default Vue.extend({
|
|||
]
|
||||
}
|
||||
|
||||
const response = await this.showSaveDialog(options)
|
||||
const response = await showSaveDialog(options)
|
||||
if (wasPlaying) {
|
||||
this.player.play()
|
||||
}
|
||||
|
@ -1921,8 +1921,7 @@ export default Vue.extend({
|
|||
'updateDefaultCaptionSettings',
|
||||
'parseScreenshotCustomFileName',
|
||||
'updateScreenshotFolderPath',
|
||||
'getPicturesPath',
|
||||
'showSaveDialog'
|
||||
'getPicturesPath'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { IpcChannels } from '../../constants'
|
||||
import FtToastEvents from '../components/ft-toast/ft-toast-events'
|
||||
import i18n from '../i18n/index'
|
||||
import fs from 'fs'
|
||||
|
||||
export const colors = [
|
||||
{ name: 'Red', value: '#d50000' },
|
||||
|
@ -246,6 +247,133 @@ export function openExternalLink(url) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function showOpenDialog (options) {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
const { ipcRenderer } = require('electron')
|
||||
return await ipcRenderer.invoke(IpcChannels.SHOW_OPEN_DIALOG, options)
|
||||
} else {
|
||||
return await new Promise((resolve) => {
|
||||
const fileInput = document.createElement('input')
|
||||
fileInput.setAttribute('type', 'file')
|
||||
if (options?.filters[0]?.extensions !== undefined) {
|
||||
// this will map the given extensions from the options to the accept attribute of the input
|
||||
fileInput.setAttribute('accept', options.filters[0].extensions.map((extension) => { return `.${extension}` }).join(', '))
|
||||
}
|
||||
fileInput.onchange = () => {
|
||||
const files = Array.from(fileInput.files)
|
||||
resolve({ canceled: false, files, filePaths: files.map(({ name }) => { return name }) })
|
||||
delete fileInput.onchange
|
||||
}
|
||||
const listenForEnd = () => {
|
||||
window.removeEventListener('focus', listenForEnd)
|
||||
// 1 second timeout on the response from the file picker to prevent awaiting forever
|
||||
setTimeout(() => {
|
||||
if (fileInput.files.length === 0 && typeof fileInput.onchange === 'function') {
|
||||
// if there are no files and the onchange has not been triggered, the file-picker was canceled
|
||||
resolve({ canceled: true })
|
||||
delete fileInput.onchange
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
window.addEventListener('focus', listenForEnd)
|
||||
fileInput.click()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} response the response from `showOpenDialog`
|
||||
* @param {number} index which file to read (defaults to the first in the response)
|
||||
* @returns the text contents of the selected file
|
||||
*/
|
||||
export function readFileFromDialog(response, index = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
// if this is Electron, use fs
|
||||
fs.readFile(response.filePaths[index], (err, data) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(new TextDecoder('utf-8').decode(data))
|
||||
})
|
||||
} else {
|
||||
// if this is web, use FileReader
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
reader.onload = function (file) {
|
||||
resolve(file.currentTarget.result)
|
||||
}
|
||||
reader.readAsText(response.files[index])
|
||||
} catch (exception) {
|
||||
reject(exception)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function showSaveDialog (options) {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
const { ipcRenderer } = require('electron')
|
||||
return await ipcRenderer.invoke(IpcChannels.SHOW_SAVE_DIALOG, options)
|
||||
} else {
|
||||
// If the native filesystem api is available
|
||||
if ('showSaveFilePicker' in window) {
|
||||
return {
|
||||
canceled: false,
|
||||
handle: await window.showSaveFilePicker({
|
||||
suggestedName: options.defaultPath.split('/').at(-1),
|
||||
types: options.filters[0]?.extensions?.map((extension) => {
|
||||
return {
|
||||
accept: {
|
||||
'application/octet-stream': '.' + extension
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return { canceled: false, filePath: options.defaultPath }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to a file picked out from the `showSaveDialog` picker
|
||||
* @param {object} response the response from `showSaveDialog`
|
||||
* @param {string} content the content to be written to the file selected by the dialog
|
||||
*/
|
||||
export async function writeFileFromDialog (response, content) {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const { filePath } = response
|
||||
fs.writeFile(filePath, content, (error) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if ('showOpenFilePicker' in window) {
|
||||
const { handle } = response
|
||||
const writableStream = await handle.createWritable()
|
||||
await writableStream.write(content)
|
||||
await writableStream.close()
|
||||
} else {
|
||||
// If the native filesystem api is not available,
|
||||
const { filePath } = response
|
||||
const filename = filePath.split('/').at(-1)
|
||||
const a = document.createElement('a')
|
||||
const url = URL.createObjectURL(new Blob([content], { type: 'application/octet-stream' }))
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', encodeURI(filename))
|
||||
a.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates an absolute web url from a given path.
|
||||
* It will assume all given paths are relative to the current window location.
|
||||
|
|
|
@ -4,7 +4,7 @@ import path from 'path'
|
|||
import i18n from '../../i18n/index'
|
||||
|
||||
import { IpcChannels } from '../../../constants'
|
||||
import { createWebURL, openExternalLink, showToast } from '../../helpers/utils'
|
||||
import { createWebURL, openExternalLink, showSaveDialog, showToast } from '../../helpers/utils'
|
||||
|
||||
const state = {
|
||||
isSideNavOpen: false,
|
||||
|
@ -170,7 +170,7 @@ const actions = {
|
|||
}
|
||||
]
|
||||
}
|
||||
const response = await dispatch('showSaveDialog', { options })
|
||||
const response = await showSaveDialog(options)
|
||||
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
|
@ -244,129 +244,6 @@ const actions = {
|
|||
return (await invokeIRC(context, IpcChannels.GET_SYSTEM_LOCALE, webCbk)) || 'en-US'
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} response the response from `showOpenDialog`
|
||||
* @param {Number} index which file to read (defaults to the first in the response)
|
||||
* @returns the text contents of the selected file
|
||||
*/
|
||||
async readFileFromDialog(context, { response, index = 0 }) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
// if this is Electron, use fs
|
||||
fs.readFile(response.filePaths[index], (err, data) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(new TextDecoder('utf-8').decode(data))
|
||||
})
|
||||
} else {
|
||||
// if this is web, use FileReader
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
reader.onload = function (file) {
|
||||
resolve(file.currentTarget.result)
|
||||
}
|
||||
reader.readAsText(response.files[index])
|
||||
} catch (exception) {
|
||||
reject(exception)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async showOpenDialog (context, options) {
|
||||
const webCbk = () => {
|
||||
return new Promise((resolve) => {
|
||||
const fileInput = document.createElement('input')
|
||||
fileInput.setAttribute('type', 'file')
|
||||
if (options?.filters[0]?.extensions !== undefined) {
|
||||
// this will map the given extensions from the options to the accept attribute of the input
|
||||
fileInput.setAttribute('accept', options.filters[0].extensions.map((extension) => { return `.${extension}` }).join(', '))
|
||||
}
|
||||
fileInput.onchange = () => {
|
||||
const files = Array.from(fileInput.files)
|
||||
resolve({ canceled: false, files, filePaths: files.map(({ name }) => { return name }) })
|
||||
delete fileInput.onchange
|
||||
}
|
||||
const listenForEnd = () => {
|
||||
window.removeEventListener('focus', listenForEnd)
|
||||
// 1 second timeout on the response from the file picker to prevent awaiting forever
|
||||
setTimeout(() => {
|
||||
if (fileInput.files.length === 0 && typeof fileInput.onchange === 'function') {
|
||||
// if there are no files and the onchange has not been triggered, the file-picker was canceled
|
||||
resolve({ canceled: true })
|
||||
delete fileInput.onchange
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
window.addEventListener('focus', listenForEnd)
|
||||
fileInput.click()
|
||||
})
|
||||
}
|
||||
return await invokeIRC(context, IpcChannels.SHOW_OPEN_DIALOG, webCbk, options)
|
||||
},
|
||||
|
||||
/**
|
||||
* Write to a file picked out from the `showSaveDialog` picker
|
||||
* @param {Object} response the response from `showSaveDialog`
|
||||
* @param {String} content the content to be written to the file selected by the dialog
|
||||
*/
|
||||
async writeFileFromDialog (context, { response, content }) {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const { filePath } = response
|
||||
fs.writeFile(filePath, content, (error) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if ('showOpenFilePicker' in window) {
|
||||
const { handle } = response
|
||||
const writableStream = await handle.createWritable()
|
||||
await writableStream.write(content)
|
||||
await writableStream.close()
|
||||
} else {
|
||||
// If the native filesystem api is not available,
|
||||
const { filePath } = response
|
||||
const filename = filePath.split('/').at(-1)
|
||||
const a = document.createElement('a')
|
||||
const url = URL.createObjectURL(new Blob([content], { type: 'application/octet-stream' }))
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', encodeURI(filename))
|
||||
a.click()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async showSaveDialog (context, options) {
|
||||
const webCbk = async () => {
|
||||
// If the native filesystem api is available
|
||||
if ('showSaveFilePicker' in window) {
|
||||
return {
|
||||
canceled: false,
|
||||
handle: await window.showSaveFilePicker({
|
||||
suggestedName: options.defaultPath.split('/').at(-1),
|
||||
types: options?.filters[0]?.extensions?.map((extension) => {
|
||||
return {
|
||||
accept: {
|
||||
'application/octet-stream': '.' + extension
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return { canceled: false, filePath: options.defaultPath }
|
||||
}
|
||||
}
|
||||
return await invokeIRC(context, IpcChannels.SHOW_SAVE_DIALOG, webCbk, options)
|
||||
},
|
||||
|
||||
async getUserDataPath (context) {
|
||||
// TODO: implement getUserDataPath web compatible callback
|
||||
const webCbk = () => null
|
||||
|
|
Loading…
Reference in New Issue