|
| 1 | +import IDBMap from "@webreflection/idb-map"; |
| 2 | +import { assign } from "polyscript/exports"; |
| 3 | +import { $$ } from "basic-devtools"; |
| 4 | + |
| 5 | +const stop = (event) => { |
| 6 | + event.preventDefault(); |
| 7 | + event.stopImmediatePropagation(); |
| 8 | +}; |
| 9 | + |
| 10 | +// ⚠️ these two constants MUST be passed as `fs` |
| 11 | +// within the worker onBeforeRunAsync hook! |
| 12 | +export const NAMESPACE = "@pyscript.fs"; |
| 13 | +export const ERROR = "storage permissions not granted"; |
| 14 | + |
| 15 | +export const idb = new IDBMap(NAMESPACE); |
| 16 | + |
| 17 | +/** |
| 18 | + * Ask a user action via dialog and returns the directory handler once granted. |
| 19 | + * @param {{id?:string, mode?:"read"|"readwrite", hint?:"desktop"|"documents"|"downloads"|"music"|"pictures"|"videos"}} options |
| 20 | + * @returns {Promise<FileSystemDirectoryHandle>} |
| 21 | + */ |
| 22 | +export const getFileSystemDirectoryHandle = async (options) => { |
| 23 | + if (!("showDirectoryPicker" in globalThis)) { |
| 24 | + return Promise.reject( |
| 25 | + new Error("showDirectoryPicker is not supported"), |
| 26 | + ); |
| 27 | + } |
| 28 | + |
| 29 | + const { promise, resolve, reject } = Promise.withResolvers(); |
| 30 | + |
| 31 | + const how = { id: "pyscript", mode: "readwrite", ...options }; |
| 32 | + if (options.hint) how.startIn = options.hint; |
| 33 | + |
| 34 | + const transient = async () => { |
| 35 | + try { |
| 36 | + /* eslint-disable */ |
| 37 | + const handler = await showDirectoryPicker(how); |
| 38 | + /* eslint-enable */ |
| 39 | + if ((await handler.requestPermission(how)) === "granted") { |
| 40 | + resolve(handler); |
| 41 | + return true; |
| 42 | + } |
| 43 | + } catch ({ message }) { |
| 44 | + console.warn(message); |
| 45 | + } |
| 46 | + return false; |
| 47 | + }; |
| 48 | + |
| 49 | + // in case the user decided to attach the event itself |
| 50 | + // as opposite of relying our dialog walkthrough |
| 51 | + if (navigator.userActivation?.isActive) { |
| 52 | + if (!(await transient())) reject(new Error(ERROR)); |
| 53 | + } else { |
| 54 | + const dialog = assign(document.createElement("dialog"), { |
| 55 | + className: "pyscript-fs", |
| 56 | + innerHTML: [ |
| 57 | + "<strong>ℹ️ Persistent FileSystem</strong><hr>", |
| 58 | + "<p><small>PyScript would like to access a local folder.</small></p>", |
| 59 | + "<div><button title='ok'>✅ Authorize</button>", |
| 60 | + "<button title='cancel'>❌</button></div>", |
| 61 | + ].join(""), |
| 62 | + }); |
| 63 | + |
| 64 | + const [ok, cancel] = $$("button", dialog); |
| 65 | + |
| 66 | + ok.addEventListener("click", async (event) => { |
| 67 | + stop(event); |
| 68 | + if (await transient()) dialog.close(); |
| 69 | + }); |
| 70 | + |
| 71 | + cancel.addEventListener("click", async (event) => { |
| 72 | + stop(event); |
| 73 | + reject(new Error(ERROR)); |
| 74 | + dialog.close(); |
| 75 | + }); |
| 76 | + |
| 77 | + document.body.appendChild(dialog).showModal(); |
| 78 | + } |
| 79 | + |
| 80 | + return promise; |
| 81 | +}; |
0 commit comments