sliftutils 1.4.63 → 1.4.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builders/electronBuild.ts +1 -1
- package/builders/extensionBuild.ts +1 -1
- package/builders/nodeJSBuild.ts +1 -1
- package/builders/webBuild.ts +1 -1
- package/bundler/bundleEntry.ts +1 -1
- package/index.d.ts +4 -0
- package/package.json +1 -1
- package/storage/FileFolderAPI.d.ts +4 -0
- package/storage/FileFolderAPI.tsx +89 -10
- package/test.ts +1 -1
|
@@ -109,5 +109,5 @@ async function main() {
|
|
|
109
109
|
let duration = Date.now() - time;
|
|
110
110
|
console.log(`Electron build completed in ${formatTime(duration)}`);
|
|
111
111
|
}
|
|
112
|
-
main().catch(console.error).finally(() => process.exit());
|
|
112
|
+
main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
|
|
113
113
|
|
|
@@ -141,4 +141,4 @@ async function main() {
|
|
|
141
141
|
let duration = Date.now() - time;
|
|
142
142
|
console.log(`Extension build completed in ${formatTime(duration)}`);
|
|
143
143
|
}
|
|
144
|
-
main().catch(console.error).finally(() => process.exit());
|
|
144
|
+
main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
|
package/builders/nodeJSBuild.ts
CHANGED
|
@@ -26,4 +26,4 @@ async function main() {
|
|
|
26
26
|
let duration = Date.now() - time;
|
|
27
27
|
console.log(`NodeJS build completed in ${formatTime(duration)}`);
|
|
28
28
|
}
|
|
29
|
-
main().catch(console.error).finally(() => process.exit());
|
|
29
|
+
main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
|
package/builders/webBuild.ts
CHANGED
|
@@ -92,5 +92,5 @@ async function main() {
|
|
|
92
92
|
console.log(`Web build completed in ${formatTime(duration)}`);
|
|
93
93
|
console.log(`file://${path.resolve(indexHtmlOutput).replaceAll("\\", "/")}`);
|
|
94
94
|
}
|
|
95
|
-
main().catch(console.error).finally(() => process.exit());
|
|
95
|
+
main().catch(err => { console.error(err); process.exitCode = 1; }).finally(() => process.exit());
|
|
96
96
|
|
package/bundler/bundleEntry.ts
CHANGED
package/index.d.ts
CHANGED
|
@@ -1823,6 +1823,10 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1823
1823
|
}): Promise<DirectoryWrapper>;
|
|
1824
1824
|
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
1825
1825
|
}
|
|
1826
|
+
export declare function usePrivateFileSystem(): void;
|
|
1827
|
+
export declare function isPrivateFileSystemActive(): boolean;
|
|
1828
|
+
export declare function listPrivateFolders(): Promise<string[]>;
|
|
1829
|
+
export declare function pickPrivateFolder(name: string): void;
|
|
1826
1830
|
export declare const getDirectoryHandle: {
|
|
1827
1831
|
(): Promise<DirectoryWrapper>;
|
|
1828
1832
|
reset(): void;
|
package/package.json
CHANGED
|
@@ -106,6 +106,10 @@ export declare class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
|
106
106
|
}): Promise<DirectoryWrapper>;
|
|
107
107
|
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
108
108
|
}
|
|
109
|
+
export declare function usePrivateFileSystem(): void;
|
|
110
|
+
export declare function isPrivateFileSystemActive(): boolean;
|
|
111
|
+
export declare function listPrivateFolders(): Promise<string[]>;
|
|
112
|
+
export declare function pickPrivateFolder(name: string): void;
|
|
109
113
|
export declare const getDirectoryHandle: {
|
|
110
114
|
(): Promise<DirectoryWrapper>;
|
|
111
115
|
reset(): void;
|
|
@@ -372,6 +372,71 @@ export class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
|
|
375
|
+
// When set, getDirectoryHandle skips every prompt + remote check and serves from the browser's Origin
|
|
376
|
+
// Private File System (OPFS). One named subfolder is "current" per fileAPIKey; pickPrivateFolder /
|
|
377
|
+
// resetStorageLocation switch which one. Set this in app startup BEFORE the first getDirectoryHandle.
|
|
378
|
+
let opfsEnabled = false;
|
|
379
|
+
const OPFS_FOLDERS_DIR = "folders";
|
|
380
|
+
const OPFS_DEFAULT_FOLDER = "default";
|
|
381
|
+
function opfsCurrentKey() { return getFileAPIKey() + ":opfs:current"; }
|
|
382
|
+
function getCurrentOpfsFolder(): string {
|
|
383
|
+
return localStorage.getItem(opfsCurrentKey()) || OPFS_DEFAULT_FOLDER;
|
|
384
|
+
}
|
|
385
|
+
function setCurrentOpfsFolder(name: string): void {
|
|
386
|
+
localStorage.setItem(opfsCurrentKey(), name);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Switches getDirectoryHandle to the Origin Private File System. No directory picker, no permission
|
|
390
|
+
// prompt, no remote server check. Persists nothing — call on every app start before reading storage.
|
|
391
|
+
export function usePrivateFileSystem(): void {
|
|
392
|
+
opfsEnabled = true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Whether the OPFS branch is in effect (so other callers can adapt — e.g. skip the "find data subdir"
|
|
396
|
+
// hack in getFileStorageNested2 since OPFS is always a clean root we own end-to-end).
|
|
397
|
+
export function isPrivateFileSystemActive(): boolean {
|
|
398
|
+
return opfsEnabled;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function getOpfsFoldersDir(): Promise<FileSystemDirectoryHandle> {
|
|
402
|
+
if (!navigator.storage?.getDirectory) {
|
|
403
|
+
throw new Error("Private File System Access API not supported in this browser");
|
|
404
|
+
}
|
|
405
|
+
if (location.protocol === "file:") {
|
|
406
|
+
throw new Error("Private File System API is disallowed in file:// locations. Host on an actual origin.");
|
|
407
|
+
}
|
|
408
|
+
const root = await navigator.storage.getDirectory();
|
|
409
|
+
const keyed = await root.getDirectoryHandle(getFileAPIKey(), { create: true });
|
|
410
|
+
return await keyed.getDirectoryHandle(OPFS_FOLDERS_DIR, { create: true });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function getCurrentOpfsHandle(): Promise<DirectoryWrapper> {
|
|
414
|
+
const folders = await getOpfsFoldersDir();
|
|
415
|
+
const folder = await folders.getDirectoryHandle(getCurrentOpfsFolder(), { create: true });
|
|
416
|
+
return folder as unknown as DirectoryWrapper;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// All previously-used OPFS subfolders under this fileAPIKey, sorted alphabetically (timestamp-named
|
|
420
|
+
// auto-generated folders therefore sort chronologically). Use this to surface a list, then call
|
|
421
|
+
// pickPrivateFolder(name) to switch.
|
|
422
|
+
export async function listPrivateFolders(): Promise<string[]> {
|
|
423
|
+
if (!navigator.storage?.getDirectory) return [];
|
|
424
|
+
let folders: FileSystemDirectoryHandle;
|
|
425
|
+
try { folders = await getOpfsFoldersDir(); } catch { return []; }
|
|
426
|
+
const names: string[] = [];
|
|
427
|
+
for await (const [name, handle] of (folders as unknown as AsyncIterable<[string, { kind: string }]>)) {
|
|
428
|
+
if (handle.kind === "directory") names.push(name);
|
|
429
|
+
}
|
|
430
|
+
return names.sort();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Switch to a specific OPFS subfolder and reload so getDirectoryHandle re-resolves to it. The folder
|
|
434
|
+
// is created if it doesn't exist yet.
|
|
435
|
+
export function pickPrivateFolder(name: string): void {
|
|
436
|
+
setCurrentOpfsFolder(name);
|
|
437
|
+
window.location.reload();
|
|
438
|
+
}
|
|
439
|
+
|
|
375
440
|
// Returns the directory handle to use — local (Node / picked folder) OR a remote server, both as the
|
|
376
441
|
// same DirectoryWrapper, so callers don't know or care which it is. A remembered server is VERIFIED
|
|
377
442
|
// (we actually connect) before use; if it no longer works we re-prompt, just like a local folder whose
|
|
@@ -380,6 +445,9 @@ export const getDirectoryHandle = lazy(async function getDirectoryHandle(): Prom
|
|
|
380
445
|
if (isNode()) {
|
|
381
446
|
return new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
|
|
382
447
|
}
|
|
448
|
+
if (opfsEnabled) {
|
|
449
|
+
return getCurrentOpfsHandle();
|
|
450
|
+
}
|
|
383
451
|
|
|
384
452
|
// A server connected in a previous session: only reuse it if it still actually works.
|
|
385
453
|
const savedRemote = loadRemoteConfig();
|
|
@@ -479,17 +547,21 @@ export const getFileStorageNested2 = cache(async function getFileStorage(pathStr
|
|
|
479
547
|
base = new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
|
|
480
548
|
} else {
|
|
481
549
|
base = await getDirectoryHandle();
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
550
|
+
// Skip the "find data subdir" hack under OPFS — that's for picked directories where the user
|
|
551
|
+
// might have selected a folder with unrelated files; OPFS is a clean root we own end-to-end.
|
|
552
|
+
if (!opfsEnabled) {
|
|
553
|
+
let dirs: string[] = [];
|
|
554
|
+
let alls: string[] = [];
|
|
555
|
+
for await (const [name, entry] of base) {
|
|
556
|
+
if (entry.kind === "directory") {
|
|
557
|
+
dirs.push(name);
|
|
558
|
+
}
|
|
559
|
+
alls.push(name);
|
|
560
|
+
}
|
|
561
|
+
// HACK: If there are enough files, it's almost certainly not a directory that contains collections. It's probably the user's data instead
|
|
562
|
+
if (dirs.includes(".git") || dirs.includes("data") || alls.length > 100) {
|
|
563
|
+
base = await base.getDirectoryHandle("data", { create: true });
|
|
487
564
|
}
|
|
488
|
-
alls.push(name);
|
|
489
|
-
}
|
|
490
|
-
// HACK: If there are enough files, it's almost certainly not a directory that contains collections. It's probably the user's data instead
|
|
491
|
-
if (dirs.includes(".git") || dirs.includes("data") || alls.length > 100) {
|
|
492
|
-
base = await base.getDirectoryHandle("data", { create: true });
|
|
493
565
|
}
|
|
494
566
|
}
|
|
495
567
|
for (let part of pathStr.split("/")) {
|
|
@@ -506,6 +578,13 @@ export const getFileStorage = lazy(async function getFileStorage(): Promise<File
|
|
|
506
578
|
return wrapHandle(handle);
|
|
507
579
|
});
|
|
508
580
|
export function resetStorageLocation() {
|
|
581
|
+
if (opfsEnabled) {
|
|
582
|
+
// Don't delete previous folder — listPrivateFolders / pickPrivateFolder still need it. Just point
|
|
583
|
+
// "current" at a fresh timestamp-named one so the next reload starts clean.
|
|
584
|
+
setCurrentOpfsFolder(new Date().toISOString().replace(/[:.]/g, "-"));
|
|
585
|
+
window.location.reload();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
509
588
|
localStorage.removeItem(getFileAPIKey());
|
|
510
589
|
try { localStorage.removeItem(remoteConfigKey()); } catch { /* ignore */ }
|
|
511
590
|
window.location.reload();
|
package/test.ts
CHANGED