|
let db = new Promise((resolve, reject) => { |
|
const request = indexedDB.open("FileStore"); |
|
request.onerror = reject; |
|
request.onsuccess = (event) => { |
|
resolve(event.target.result); |
|
}; |
|
request.onupgradeneeded = (event) => { |
|
const db = event.target.result; |
|
const objectStore = db.createObjectStore("handles"); |
|
}; |
|
}); |
|
|
|
async function setStoredFileHandle(fileHandle, key) { |
|
const transaction = (await db).transaction(["handles"], "readwrite"); |
|
const objectStore = transaction.objectStore("handles"); |
|
const objectStoreRequest = objectStore.put(fileHandle, key); |
|
} |
|
|
|
async function getStoredFileHandle(key) { |
|
const transaction = (await db).transaction(["handles"], "readonly"); |
|
transaction.oncomplete = (e) => console.log('transaction complete', e); |
|
transaction.onerror = (e) => console.error('transaction error', e); |
|
|
|
const objectStore = transaction.objectStore("handles"); |
|
const request = objectStore.get(key); |
|
|
|
const deferred = Promise.withResolvers(); |
|
request.onerror = deferred.reject; |
|
request.onsuccess = (event) => { |
|
const handle = request.result; |
|
deferred.resolve(handle); |
|
} |
|
|
|
return deferred.promise; |
|
} |
|
|
|
async function checkFilePermissions(handle, {mode = "readwrite", autoRequest = true} = {}) { |
|
const modes = ["read", "readwrite"] |
|
if (!modes.includes(mode)) |
|
throw new Error(`The "mode" option must be either "read" or "readwrite", received "${mode}"`); |
|
|
|
const options = { mode }; |
|
const currentGrant = await handle.queryPermission(options); |
|
if (currentGrant === 'granted') |
|
return 'granted'; |
|
|
|
if (autoRequest) { |
|
const grantRequest = await handle.requestPermission(options); |
|
if (grantRequest === 'granted') { |
|
return 'granted'; |
|
} |
|
} |
|
|
|
return 'denied'; |
|
} |
|
|
|
const inputLocation = document.getElementById('backup-location'); |
|
|
|
inputLocation.addEventListener("change", async (event) => { |
|
const file = event.target.files[0]; |
|
|
|
const handle = await file.getAsFileSystemHandle(); |
|
|
|
showBackupFileInfo(handle, backupFileIDBKey); |
|
readBackupFile(handle); |
|
|
|
setStoredFileHandle(handle, backupFileIDBKey); |
|
writeBackupFile(handle); |
|
// console.log(event); |
|
// let output = document.getElementById('listing'); |
|
// for (const file of event.target.files) { |
|
// let item = document.createElement('li'); |
|
// item.textContent = file.webkitRelativePath; |
|
// output.appendChild(item); |
|
// } |
|
}); |
|
|
|
const backupFileIDBKey = 'backup-file'; |
|
const backupDirectoryIDBKey = 'backup-dir'; |
|
|
|
(async function() { |
|
const handle = await getStoredFileHandle(backupFileIDBKey); |
|
showBackupFileInfo(handle, backupFileIDBKey); |
|
checkFilePermissions(handle); |
|
readBackupFile(handle); |
|
|
|
// const dt = new DataTransfer(); |
|
// const file = await handle.getFile(); |
|
// dt.items.add(file); |
|
// inputLocation.files = dt.files; |
|
})(); |
|
|
|
|
|
const createBackupButton = document.getElementById('create-backup-file'); |
|
createBackupButton.addEventListener('click', async (event) => { |
|
const handle = await createFile('backup.json'); |
|
if (!handle) return; |
|
showBackupFileInfo(handle, backupFileIDBKey); |
|
setStoredFileHandle(handle, backupFileIDBKey); |
|
writeBackupFile(handle); |
|
}); |
|
|
|
const selectBackupButton = document.getElementById('select-backup-file'); |
|
selectBackupButton.addEventListener('click', async (event) => { |
|
const [handle] = await window.showOpenFilePicker({ |
|
types: [ |
|
{ |
|
description: "JSON files", |
|
accept: { |
|
'application/json': ['.json'], |
|
} |
|
} |
|
], |
|
excludeAcceptAllOption: true, |
|
multiple: false, |
|
}); |
|
|
|
if (!handle) return; |
|
setStoredFileHandle(handle, backupFileIDBKey); |
|
showBackupFileInfo(handle, backupFileIDBKey); |
|
writeBackupFile(handle); |
|
}); |
|
|
|
const selectBackupDirButton = document.getElementById('select-backup-directory'); |
|
selectBackupDirButton.addEventListener('click', async (event) => { |
|
const dirHandle = await window.showDirectoryPicker(); |
|
setStoredFileHandle(dirHandle, backupDirectoryIDBKey); |
|
|
|
const handle = await dirHandle.getFileHandle('back-it-up.json', {create: true}); |
|
|
|
if (!handle) return; |
|
setStoredFileHandle(handle, backupFileIDBKey); |
|
showBackupFileInfo(handle, backupFileIDBKey); |
|
writeBackupFile(handle); |
|
}); |
|
|
|
const kindDisplay = document.getElementById('kind-value'); |
|
const nameDisplay = document.getElementById('name-value'); |
|
const idbKeyDisplay = document.getElementById('idb-key-value'); |
|
function showBackupFileInfo(handle, key) { |
|
kindDisplay.innerText = handle.kind; |
|
nameDisplay.innerText = handle.name; |
|
idbKeyDisplay.innerText = key; |
|
} |
|
|
|
async function createFile(suggestedName) { |
|
try { |
|
// NOTE: not available on Firefox or Safari |
|
const handle = await window.showSaveFilePicker({ |
|
suggestedName, |
|
types: [{ |
|
description: 'Application backup file', |
|
accept: { |
|
'application/json': ['.json'], |
|
}, |
|
}], |
|
}); |
|
return handle; |
|
} catch (e) { |
|
console.error('Unexpected error creating file.', e); |
|
return null; |
|
} |
|
} |
|
|
|
class SimpleFile { |
|
async readAsText(fileHandle, encoding = "utf-8") { |
|
return new Promise(async (resolve, reject) => { |
|
const file = await fileHandle.getFile(); |
|
const reader = new FileReader(); |
|
reader.onload = (event) => resolve(event.target.result); |
|
reader.onerror = reject; |
|
reader.readAsText(file, encoding); |
|
}); |
|
} |
|
|
|
async readAsJSON(fileHandle, encoding = "utf-8") { |
|
const file = await this.readAsText(fileHandle, encoding); |
|
return JSON.parse(file); |
|
} |
|
|
|
// NOTE: Strings are automatically encoded as UTF-8, no special action needed. |
|
async writeAsText(fileHandle, body, {encoding} = {}) { |
|
const writable = await fileHandle.createWritable(); |
|
await writable.write(body); |
|
await writable.close(); |
|
} |
|
|
|
async writeAsJSON(fileHandle, body, {space = 0, encoding = 'utf-8'} = {}) { |
|
return this.writeAsText(fileHandle, JSON.stringify(body, null, space)) |
|
} |
|
} |
|
|
|
const file = new SimpleFile(); |
|
function writeBackupFile(handle) { |
|
file.writeAsJSON(handle, {greeting: "Hello, world!"}); |
|
} |
|
|
|
async function readBackupFile(handle) { |
|
const foo = await file.readAsJSON(handle); |
|
console.log('backup file', foo); |
|
} |
|
|
|
// --- |
|
|
|
const dropTarget = document.getElementById('drop-target'); |
|
dropTarget.addEventListener('dragenter', (event) => { |
|
event.stopPropagation(); |
|
event.preventDefault(); |
|
}) |
|
dropTarget.addEventListener('dragover', (event) => { |
|
event.stopPropagation(); |
|
event.preventDefault(); |
|
}) |
|
dropTarget.addEventListener('drop', async (event) => { |
|
event.stopPropagation(); |
|
event.preventDefault(); |
|
|
|
const items = event.dataTransfer.items; |
|
|
|
// The `getAsFileSystemHandle()` call must happen on the same tick as entry |
|
// into the event handler. |
|
// |
|
// `item.webkitGetAsEntry()` is more broadly supported, but does not |
|
// provide write access to files/directories. |
|
const handlesPromises = [...items] |
|
.filter((file) => file.kind === "file") |
|
.map((file) => file.getAsFileSystemHandle()); |
|
const handles = await Promise.all(handlesPromises); |
|
|
|
console.log(handles); |
|
}) |