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 { opmlToJSON } from 'opml-to-json'
|
||||||
import ytch from 'yt-channel-info'
|
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({
|
export default Vue.extend({
|
||||||
name: 'DataSettings',
|
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) {
|
if (response.canceled || response.filePaths?.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let textDecode
|
let textDecode
|
||||||
try {
|
try {
|
||||||
textDecode = await this.readFileFromDialog({ response })
|
textDecode = await readFileFromDialog(response)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||||
showToast(`${message}: ${err}`)
|
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 === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: subscriptionsDb })
|
await writeFileFromDialog(response, subscriptionsDb)
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
showToast(`${message}: ${writeErr}`)
|
||||||
|
@ -568,14 +577,14 @@ export default Vue.extend({
|
||||||
return object
|
return object
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await this.showSaveDialog(options)
|
const response = await showSaveDialog(options)
|
||||||
if (response.canceled || response.filePath === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: JSON.stringify(subscriptionsObject) })
|
await writeFileFromDialog(response, JSON.stringify(subscriptionsObject))
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
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 === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: opmlData })
|
await writeFileFromDialog(response, opmlData)
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
showToast(`${message}: ${writeErr}`)
|
||||||
|
@ -652,14 +661,14 @@ export default Vue.extend({
|
||||||
exportText += `${channel.id},${channelUrl},${channelName}\n`
|
exportText += `${channel.id},${channelUrl},${channelName}\n`
|
||||||
})
|
})
|
||||||
exportText += '\n'
|
exportText += '\n'
|
||||||
const response = await this.showSaveDialog(options)
|
const response = await showSaveDialog(options)
|
||||||
if (response.canceled || response.filePath === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: exportText })
|
await writeFileFromDialog(response, exportText)
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
showToast(`${message}: ${writeErr}`)
|
||||||
|
@ -699,13 +708,13 @@ export default Vue.extend({
|
||||||
newPipeObject.subscriptions.push(subscription)
|
newPipeObject.subscriptions.push(subscription)
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await this.showSaveDialog(options)
|
const response = await showSaveDialog(options)
|
||||||
if (response.canceled || response.filePath === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: JSON.stringify(newPipeObject) })
|
await writeFileFromDialog(response, JSON.stringify(newPipeObject))
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
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) {
|
if (response.canceled || response.filePaths?.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let textDecode
|
let textDecode
|
||||||
try {
|
try {
|
||||||
textDecode = await this.readFileFromDialog({ response })
|
textDecode = await readFileFromDialog(response)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||||
showToast(`${message}: ${err}`)
|
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 === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: historyDb })
|
await writeFileFromDialog(response, historyDb)
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
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) {
|
if (response.canceled || response.filePaths?.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = await this.readFileFromDialog({ response })
|
data = await readFileFromDialog(response)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to read file')
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||||
showToast(`${message}: ${err}`)
|
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 === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.writeFileFromDialog({ response, content: JSON.stringify(this.allPlaylists) })
|
await writeFileFromDialog(response, JSON.stringify(this.allPlaylists))
|
||||||
} catch (writeErr) {
|
} catch (writeErr) {
|
||||||
const message = this.$t('Settings.Data Settings.Unable to write file')
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
showToast(`${message}: ${writeErr}`)
|
showToast(`${message}: ${writeErr}`)
|
||||||
|
@ -1100,10 +1109,6 @@ export default Vue.extend({
|
||||||
'updateShowProgressBar',
|
'updateShowProgressBar',
|
||||||
'updateHistory',
|
'updateHistory',
|
||||||
'compactHistory',
|
'compactHistory',
|
||||||
'showOpenDialog',
|
|
||||||
'readFileFromDialog',
|
|
||||||
'showSaveDialog',
|
|
||||||
'writeFileFromDialog',
|
|
||||||
'getUserDataPath',
|
'getUserDataPath',
|
||||||
'addPlaylist',
|
'addPlaylist',
|
||||||
'addVideo'
|
'addVideo'
|
||||||
|
|
|
@ -15,7 +15,7 @@ import 'videojs-mobile-ui'
|
||||||
import 'videojs-mobile-ui/dist/videojs-mobile-ui.css'
|
import 'videojs-mobile-ui/dist/videojs-mobile-ui.css'
|
||||||
import { IpcChannels } from '../../../constants'
|
import { IpcChannels } from '../../../constants'
|
||||||
import { sponsorBlockSkipSegments } from '../../helpers/sponsorblock'
|
import { sponsorBlockSkipSegments } from '../../helpers/sponsorblock'
|
||||||
import { calculateColorLuminance, colors, showToast } from '../../helpers/utils'
|
import { calculateColorLuminance, colors, showSaveDialog, showToast } from '../../helpers/utils'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'FtVideoPlayer',
|
name: 'FtVideoPlayer',
|
||||||
|
@ -1396,7 +1396,7 @@ export default Vue.extend({
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.showSaveDialog(options)
|
const response = await showSaveDialog(options)
|
||||||
if (wasPlaying) {
|
if (wasPlaying) {
|
||||||
this.player.play()
|
this.player.play()
|
||||||
}
|
}
|
||||||
|
@ -1921,8 +1921,7 @@ export default Vue.extend({
|
||||||
'updateDefaultCaptionSettings',
|
'updateDefaultCaptionSettings',
|
||||||
'parseScreenshotCustomFileName',
|
'parseScreenshotCustomFileName',
|
||||||
'updateScreenshotFolderPath',
|
'updateScreenshotFolderPath',
|
||||||
'getPicturesPath',
|
'getPicturesPath'
|
||||||
'showSaveDialog'
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IpcChannels } from '../../constants'
|
import { IpcChannels } from '../../constants'
|
||||||
import FtToastEvents from '../components/ft-toast/ft-toast-events'
|
import FtToastEvents from '../components/ft-toast/ft-toast-events'
|
||||||
import i18n from '../i18n/index'
|
import i18n from '../i18n/index'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
export const colors = [
|
export const colors = [
|
||||||
{ name: 'Red', value: '#d50000' },
|
{ 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.
|
* This creates an absolute web url from a given path.
|
||||||
* It will assume all given paths are relative to the current window location.
|
* 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 i18n from '../../i18n/index'
|
||||||
|
|
||||||
import { IpcChannels } from '../../../constants'
|
import { IpcChannels } from '../../../constants'
|
||||||
import { createWebURL, openExternalLink, showToast } from '../../helpers/utils'
|
import { createWebURL, openExternalLink, showSaveDialog, showToast } from '../../helpers/utils'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
isSideNavOpen: false,
|
isSideNavOpen: false,
|
||||||
|
@ -170,7 +170,7 @@ const actions = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
const response = await dispatch('showSaveDialog', { options })
|
const response = await showSaveDialog(options)
|
||||||
|
|
||||||
if (response.canceled || response.filePath === '') {
|
if (response.canceled || response.filePath === '') {
|
||||||
// User canceled the save dialog
|
// User canceled the save dialog
|
||||||
|
@ -244,129 +244,6 @@ const actions = {
|
||||||
return (await invokeIRC(context, IpcChannels.GET_SYSTEM_LOCALE, webCbk)) || 'en-US'
|
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) {
|
async getUserDataPath (context) {
|
||||||
// TODO: implement getUserDataPath web compatible callback
|
// TODO: implement getUserDataPath web compatible callback
|
||||||
const webCbk = () => null
|
const webCbk = () => null
|
||||||
|
|
Loading…
Reference in New Issue