@pistonite/pure 0.22.2 → 0.23.0
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/package.json +1 -3
- package/src/env.d.ts +1 -0
- package/src/fs/FsFileImpl.ts +1 -1
- package/src/fs/FsFileStandalone.ts +21 -0
- package/src/fs/FsFileStandaloneImplFileAPI.ts +53 -0
- package/src/fs/FsFileStandaloneImplHandleAPI.ts +152 -0
- package/src/fs/FsOpen.ts +8 -0
- package/src/fs/FsOpenFile.ts +264 -0
- package/src/fs/index.ts +3 -0
- package/src/pref/injectStyle.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pistonite/pure",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pure TypeScript libraries for my projects",
|
|
6
6
|
"homepage": "https://github.com/Pistonite/pure",
|
|
@@ -32,8 +32,6 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/file-saver": "^2.0.7",
|
|
35
|
-
"eslint": "^9.19.0",
|
|
36
|
-
"typescript": "^5.7.2",
|
|
37
35
|
"vitest": "^3.0.5",
|
|
38
36
|
"mono-dev": "0.0.0"
|
|
39
37
|
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
package/src/fs/FsFileImpl.ts
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FsResult, FsVoid } from "./FsError.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for operating on a file opened standalone (without opening a file system
|
|
5
|
+
*/
|
|
6
|
+
export interface FsFileStandalone {
|
|
7
|
+
/** The name of the file */
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** If the file is writable. May prompt user for permission */
|
|
10
|
+
isWritable(): Promise<boolean>;
|
|
11
|
+
/** Get the size of the file */
|
|
12
|
+
getSize(): Promise<FsResult<number>>;
|
|
13
|
+
/** Get the content of the file as a byte array */
|
|
14
|
+
getBytes(): Promise<FsResult<Uint8Array>>;
|
|
15
|
+
/** Get the last modified time of the file */
|
|
16
|
+
getLastModified(): Promise<FsResult<number>>;
|
|
17
|
+
/** Get the text content of the file*/
|
|
18
|
+
getText(): Promise<FsResult<string>>;
|
|
19
|
+
/** Write content to the file if the implementation supports writing, and permission is granted*/
|
|
20
|
+
write(content: Uint8Array | string): Promise<FsVoid>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { errstr } from "../result";
|
|
2
|
+
|
|
3
|
+
import { fsErr, FsErr, fsFail, type FsVoid, type FsResult } from "./FsError.ts";
|
|
4
|
+
import type { FsFileStandalone } from "./FsFileStandalone.ts";
|
|
5
|
+
|
|
6
|
+
export class FsFileStandaloneImplFileAPI implements FsFileStandalone {
|
|
7
|
+
public name: string;
|
|
8
|
+
private size: number;
|
|
9
|
+
private lastModified: number;
|
|
10
|
+
private file: File;
|
|
11
|
+
|
|
12
|
+
constructor(file: File) {
|
|
13
|
+
this.name = file.name;
|
|
14
|
+
this.size = file.size;
|
|
15
|
+
this.lastModified = file.lastModified;
|
|
16
|
+
this.file = file;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async isWritable(): Promise<boolean> {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async getSize(): Promise<FsResult<number>> {
|
|
24
|
+
return { val: this.size };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async getBytes(): Promise<FsResult<Uint8Array>> {
|
|
28
|
+
try {
|
|
29
|
+
const data = await this.file.bytes();
|
|
30
|
+
return { val: data };
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error(e);
|
|
33
|
+
return { err: fsFail(errstr(e)) };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
public async getLastModified(): Promise<FsResult<number>> {
|
|
37
|
+
return { val: this.lastModified };
|
|
38
|
+
}
|
|
39
|
+
public async getText(): Promise<FsResult<string>> {
|
|
40
|
+
try {
|
|
41
|
+
const data = await this.file.text();
|
|
42
|
+
return { val: data };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error(e);
|
|
45
|
+
return { err: fsFail(errstr(e)) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
public async write(): Promise<FsVoid> {
|
|
49
|
+
return {
|
|
50
|
+
err: fsErr(FsErr.NotSupported, "Write not supported in File API"),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { errstr } from "../result";
|
|
2
|
+
|
|
3
|
+
import { fsErr, FsErr, fsFail, type FsVoid, type FsResult } from "./FsError.ts";
|
|
4
|
+
import type { FsFileStandalone } from "./FsFileStandalone.ts";
|
|
5
|
+
|
|
6
|
+
export class FsFileStandaloneImplHandleAPI implements FsFileStandalone {
|
|
7
|
+
name: string;
|
|
8
|
+
private handle: FileSystemFileHandle;
|
|
9
|
+
constructor(handle: FileSystemFileHandle) {
|
|
10
|
+
this.name = handle.name;
|
|
11
|
+
this.handle = handle;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public async isWritable(): Promise<boolean> {
|
|
15
|
+
if (
|
|
16
|
+
!("queryPermission" in this.handle) ||
|
|
17
|
+
!(typeof this.handle.queryPermission === "function")
|
|
18
|
+
) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const permission = await this.handle.queryPermission({
|
|
23
|
+
mode: "readwrite",
|
|
24
|
+
});
|
|
25
|
+
if (permission === "granted") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (permission === "denied") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (
|
|
32
|
+
!("requestPermission" in this.handle) ||
|
|
33
|
+
!(typeof this.handle.requestPermission === "function")
|
|
34
|
+
) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const requestedPermission = await this.handle.requestPermission({
|
|
38
|
+
mode: "readwrite",
|
|
39
|
+
});
|
|
40
|
+
return requestedPermission === "granted";
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error(e);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async getFile(): Promise<FsResult<File>> {
|
|
48
|
+
try {
|
|
49
|
+
return { val: await this.handle.getFile() };
|
|
50
|
+
} catch (e) {
|
|
51
|
+
if (e && typeof e === "object" && "name" in e) {
|
|
52
|
+
if (e.name === "NotAllowedError") {
|
|
53
|
+
return {
|
|
54
|
+
err: fsErr(FsErr.PermissionDenied, "Permission denied"),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (e.name === "NotFoundError") {
|
|
58
|
+
return { err: fsErr(FsErr.NotFound, "File not found") };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.error(e);
|
|
62
|
+
return { err: fsFail(errstr(e)) };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async getSize(): Promise<FsResult<number>> {
|
|
67
|
+
const file = await this.getFile();
|
|
68
|
+
if (file.err) {
|
|
69
|
+
return file;
|
|
70
|
+
}
|
|
71
|
+
return { val: file.val.size };
|
|
72
|
+
}
|
|
73
|
+
public async getBytes(): Promise<FsResult<Uint8Array>> {
|
|
74
|
+
const file = await this.getFile();
|
|
75
|
+
if (file.err) {
|
|
76
|
+
return file;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const data = await file.val.bytes();
|
|
80
|
+
return { val: data };
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(e);
|
|
83
|
+
return { err: fsFail(errstr(e)) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
public async getLastModified(): Promise<FsResult<number>> {
|
|
87
|
+
const file = await this.getFile();
|
|
88
|
+
if (file.err) {
|
|
89
|
+
return file;
|
|
90
|
+
}
|
|
91
|
+
return { val: file.val.lastModified };
|
|
92
|
+
}
|
|
93
|
+
public async getText(): Promise<FsResult<string>> {
|
|
94
|
+
const file = await this.getFile();
|
|
95
|
+
if (file.err) {
|
|
96
|
+
return file;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const data = await file.val.text();
|
|
100
|
+
return { val: data };
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.error(e);
|
|
103
|
+
return { err: fsFail(errstr(e)) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
public async write(content: Uint8Array | string): Promise<FsVoid> {
|
|
107
|
+
const writable = await this.isWritable();
|
|
108
|
+
if (!writable) {
|
|
109
|
+
return {
|
|
110
|
+
err: fsErr(
|
|
111
|
+
FsErr.NotSupported,
|
|
112
|
+
"Permission was not granted or API not supported",
|
|
113
|
+
),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const stream = await this.handle.createWritable();
|
|
118
|
+
await stream.write(content);
|
|
119
|
+
await stream.close();
|
|
120
|
+
return {};
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (e && typeof e === "object" && "name" in e) {
|
|
123
|
+
if (e.name === "NotAllowedError") {
|
|
124
|
+
return {
|
|
125
|
+
err: fsErr(FsErr.PermissionDenied, "Permission denied"),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (e.name === "NotFoundError") {
|
|
129
|
+
return { err: fsErr(FsErr.NotFound, "File not found") };
|
|
130
|
+
}
|
|
131
|
+
if (e.name === "NoMidificationAllowedError") {
|
|
132
|
+
return {
|
|
133
|
+
err: fsErr(
|
|
134
|
+
FsErr.PermissionDenied,
|
|
135
|
+
"Failed to acquire write lock",
|
|
136
|
+
),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (e.name === "AbortError") {
|
|
140
|
+
return { err: fsErr(FsErr.UserAbort, "User abort") };
|
|
141
|
+
}
|
|
142
|
+
if (e.name === "QuotaExceededError") {
|
|
143
|
+
return {
|
|
144
|
+
err: fsErr(FsErr.PermissionDenied, "Quota exceeded"),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
console.error(e);
|
|
149
|
+
return { err: fsFail(errstr(e)) };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
package/src/fs/FsOpen.ts
CHANGED
|
@@ -117,6 +117,14 @@ async function createWithPicker(
|
|
|
117
117
|
}
|
|
118
118
|
resolve(createFromFileList(files));
|
|
119
119
|
});
|
|
120
|
+
inputElement.addEventListener("cancel", () => {
|
|
121
|
+
resolve({
|
|
122
|
+
err: fsErr(
|
|
123
|
+
FsErr.UserAbort,
|
|
124
|
+
"User cancelled the operation",
|
|
125
|
+
),
|
|
126
|
+
});
|
|
127
|
+
});
|
|
120
128
|
inputElement.click();
|
|
121
129
|
},
|
|
122
130
|
);
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { errstr } from "../result";
|
|
2
|
+
|
|
3
|
+
import { fsErr, FsErr, fsFail, type FsResult } from "./FsError.ts";
|
|
4
|
+
import type { FsFileStandalone } from "./FsFileStandalone.ts";
|
|
5
|
+
import { FsFileStandaloneImplFileAPI } from "./FsFileStandaloneImplFileAPI.ts";
|
|
6
|
+
import { FsFileStandaloneImplHandleAPI } from "./FsFileStandaloneImplHandleAPI.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prompt user to open a file
|
|
10
|
+
*
|
|
11
|
+
* FileSystemAccess API is used on supported platforms, which allows writing after
|
|
12
|
+
* user grants the permission once at the time of writing to the file. If failed or not supported,
|
|
13
|
+
* the DOM input element is used as a fallback.
|
|
14
|
+
*/
|
|
15
|
+
export const fsOpenFile = async (
|
|
16
|
+
options?: FsFileOpenOptions,
|
|
17
|
+
): Promise<FsResult<FsFileStandalone>> => {
|
|
18
|
+
const result = await fsOpenFileInternal(false, options || {});
|
|
19
|
+
if (result.err) {
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
if (!result.val.length) {
|
|
23
|
+
return { err: fsErr(FsErr.UserAbort, "No files selected") };
|
|
24
|
+
}
|
|
25
|
+
return { val: result.val[0] };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Prompt user to open multiple files
|
|
30
|
+
*
|
|
31
|
+
* FileSystemAccess API is used on supported platforms, which allows writing after
|
|
32
|
+
* user grants the permission once at the time of writing to the file. If failed or not supported,
|
|
33
|
+
* the DOM input element is used as a fallback.
|
|
34
|
+
*
|
|
35
|
+
* The returned array is guaranteed to have at least 1 file.
|
|
36
|
+
*/
|
|
37
|
+
export const fsOpenFileMultiple = async (
|
|
38
|
+
options?: FsFileOpenOptions,
|
|
39
|
+
): Promise<FsResult<FsFileStandalone[]>> => {
|
|
40
|
+
const result = await fsOpenFileInternal(true, options || {});
|
|
41
|
+
if (result.err) {
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
if (!result.val.length) {
|
|
45
|
+
return { err: fsErr(FsErr.UserAbort, "No files selected") };
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const fsOpenFileInternal = async (
|
|
51
|
+
multiple: boolean,
|
|
52
|
+
options: FsFileOpenOptions,
|
|
53
|
+
): Promise<FsResult<FsFileStandalone[]>> => {
|
|
54
|
+
if (isFileSystemAccessAPISupportedForStandaloneFileOpen()) {
|
|
55
|
+
const result = await fsOpenFileWithFileSystemAccessAPI(
|
|
56
|
+
multiple,
|
|
57
|
+
options,
|
|
58
|
+
);
|
|
59
|
+
if (result.val || result.err.code === FsErr.UserAbort) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// fallback if FileSystemAccessAPI is not supported or fails
|
|
64
|
+
return fsOpenFileWithFileAPI(multiple, options);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const fsOpenFileWithFileAPI = async (
|
|
68
|
+
multiple: boolean,
|
|
69
|
+
{ types }: FsFileOpenOptions,
|
|
70
|
+
): Promise<FsResult<FsFileStandalone[]>> => {
|
|
71
|
+
const element = document.createElement("input");
|
|
72
|
+
element.type = "file";
|
|
73
|
+
if (multiple) {
|
|
74
|
+
element.multiple = true;
|
|
75
|
+
}
|
|
76
|
+
if (types?.length) {
|
|
77
|
+
const accept = new Set<string>();
|
|
78
|
+
const len = types.length;
|
|
79
|
+
for (let i = 0; i < len; i++) {
|
|
80
|
+
const acceptArray = types[i].accept;
|
|
81
|
+
const acceptLen = acceptArray.length;
|
|
82
|
+
for (let j = 0; j < acceptLen; j++) {
|
|
83
|
+
const acceptValue = acceptArray[j];
|
|
84
|
+
if (typeof acceptValue === "string") {
|
|
85
|
+
accept.add(acceptValue);
|
|
86
|
+
} else {
|
|
87
|
+
const { mime, extensions } = acceptValue;
|
|
88
|
+
if (mime) {
|
|
89
|
+
accept.add(mime);
|
|
90
|
+
}
|
|
91
|
+
if (extensions.length) {
|
|
92
|
+
extensions.forEach((ext) => {
|
|
93
|
+
if (ext) {
|
|
94
|
+
accept.add(ext);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
element.accept = Array.from(accept).join(",");
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
element.addEventListener("cancel", () => {
|
|
106
|
+
resolve({
|
|
107
|
+
err: fsErr(FsErr.UserAbort, "cancel listener invoked"),
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
element.addEventListener("change", () => {
|
|
111
|
+
if (!element.files?.length) {
|
|
112
|
+
resolve({
|
|
113
|
+
err: fsErr(FsErr.UserAbort, "no files selected"),
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const array = [];
|
|
118
|
+
for (let i = 0; i < element.files.length; i++) {
|
|
119
|
+
array.push(
|
|
120
|
+
new FsFileStandaloneImplFileAPI(element.files[i]),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
resolve({ val: array });
|
|
124
|
+
});
|
|
125
|
+
element.click();
|
|
126
|
+
});
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return { err: fsFail(errstr(e)) };
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const fsOpenFileWithFileSystemAccessAPI = async (
|
|
133
|
+
multiple: boolean,
|
|
134
|
+
{ id, types, disallowWildcard }: FsFileOpenOptions,
|
|
135
|
+
): Promise<FsResult<FsFileStandalone[]>> => {
|
|
136
|
+
const convertedTypes = types?.map((type) => {
|
|
137
|
+
const { description, accept } = type;
|
|
138
|
+
const convertedAccept: Record<string, string[]> = {};
|
|
139
|
+
const anyMimeType = [];
|
|
140
|
+
const len = accept.length;
|
|
141
|
+
for (let i = 0; i < len; i++) {
|
|
142
|
+
const acceptValue = accept[i];
|
|
143
|
+
if (typeof acceptValue === "string") {
|
|
144
|
+
if (acceptValue) {
|
|
145
|
+
anyMimeType.push(acceptValue);
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const { mime, extensions } = acceptValue;
|
|
150
|
+
if (!mime || mime === "*/*") {
|
|
151
|
+
anyMimeType.push(...extensions);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (mime in convertedAccept) {
|
|
155
|
+
convertedAccept[mime].push(...extensions);
|
|
156
|
+
} else {
|
|
157
|
+
convertedAccept[mime] = [...extensions];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (anyMimeType.length) {
|
|
161
|
+
convertedAccept["*/*"] = anyMimeType;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
description,
|
|
165
|
+
accept: convertedAccept,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
try {
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
|
+
const output = await (globalThis as any).showOpenFilePicker({
|
|
171
|
+
id,
|
|
172
|
+
excludeAcceptAllOption: types && types.length && disallowWildcard,
|
|
173
|
+
multiple,
|
|
174
|
+
types: convertedTypes,
|
|
175
|
+
});
|
|
176
|
+
const len = output.length;
|
|
177
|
+
const convertedOutput = [];
|
|
178
|
+
for (let i = 0; i < len; i++) {
|
|
179
|
+
convertedOutput.push(new FsFileStandaloneImplHandleAPI(output[i]));
|
|
180
|
+
}
|
|
181
|
+
return { val: convertedOutput };
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (e && typeof e === "object" && "name" in e) {
|
|
184
|
+
if (e.name === "AbortError") {
|
|
185
|
+
return { err: fsErr(FsErr.UserAbort, "User abort") };
|
|
186
|
+
}
|
|
187
|
+
if (e.name === "SecurityError") {
|
|
188
|
+
return { err: fsErr(FsErr.PermissionDenied, "Security error") };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { err: fsFail(errstr(e)) };
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export type FsFileOpenOptions = {
|
|
196
|
+
/**
|
|
197
|
+
* ID for the file open operation
|
|
198
|
+
*
|
|
199
|
+
* Supported implementation can use this to open the picker in the same
|
|
200
|
+
* directory for the same ID
|
|
201
|
+
*/
|
|
202
|
+
id?: string;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* If the "*.*" file type should be hidden in the picker.
|
|
206
|
+
*
|
|
207
|
+
* In unsupported implementations, this will be ignored, and the "*.*"
|
|
208
|
+
* file type will always be visible.
|
|
209
|
+
*
|
|
210
|
+
* By default, this is false
|
|
211
|
+
*/
|
|
212
|
+
disallowWildcard?: boolean;
|
|
213
|
+
|
|
214
|
+
/** List of file types to accept */
|
|
215
|
+
types?: FsFileOpenType[];
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export type FsFileOpenType = {
|
|
219
|
+
/**
|
|
220
|
+
* Optional description for the type, which may display in the file picker
|
|
221
|
+
*
|
|
222
|
+
* In unsupported implementations, this will be ignored.
|
|
223
|
+
*/
|
|
224
|
+
description?: string;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* List of file mime types or extensions to accept for this file type
|
|
228
|
+
*
|
|
229
|
+
* String elements are file extensions, with the "." prefix. More context
|
|
230
|
+
* can be provided with an object element with mime type and extensions to
|
|
231
|
+
* have better file type descriptions in the picker (if supported).
|
|
232
|
+
*/
|
|
233
|
+
accept: (FsFileOpenTypeAccept | string)[];
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export type FsFileOpenTypeAccept = {
|
|
237
|
+
/** Optional mime type, which the browser can be used to display file type descriptions */
|
|
238
|
+
mime?: string;
|
|
239
|
+
/** extensions to accept (with the "." prefix) */
|
|
240
|
+
extensions: string[];
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const isFileSystemAccessAPISupportedForStandaloneFileOpen = () => {
|
|
244
|
+
if (!globalThis || !globalThis.isSecureContext) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (
|
|
248
|
+
!("showOpenFilePicker" in globalThis) ||
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
250
|
+
typeof (globalThis as any).showOpenFilePicker !== "function"
|
|
251
|
+
) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
if (!globalThis.FileSystemFileHandle) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (!globalThis.FileSystemFileHandle.prototype.getFile) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (!globalThis.FileSystemFileHandle.prototype.createWritable) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
return true;
|
|
264
|
+
};
|
package/src/fs/index.ts
CHANGED
|
@@ -106,6 +106,7 @@ export {
|
|
|
106
106
|
fsOpenReadFrom,
|
|
107
107
|
fsOpenReadWriteFrom,
|
|
108
108
|
} from "./FsOpen.ts";
|
|
109
|
+
export { fsOpenFile, fsOpenFileMultiple } from "./FsOpenFile.ts";
|
|
109
110
|
export { fsGetSupportStatus } from "./FsSupportStatus.ts";
|
|
110
111
|
export {
|
|
111
112
|
fsRoot,
|
|
@@ -119,6 +120,7 @@ export {
|
|
|
119
120
|
export { FsErr, fsErr, fsFail } from "./FsError.ts";
|
|
120
121
|
|
|
121
122
|
export type { FsOpenRetryHandler } from "./FsOpen.ts";
|
|
123
|
+
export type * from "./FsOpenFile.ts";
|
|
122
124
|
export type { FsSupportStatus } from "./FsSupportStatus.ts";
|
|
123
125
|
export type {
|
|
124
126
|
FsFileSystem,
|
|
@@ -126,4 +128,5 @@ export type {
|
|
|
126
128
|
FsCapabilities,
|
|
127
129
|
} from "./FsFileSystem.ts";
|
|
128
130
|
export type { FsFile } from "./FsFile.ts";
|
|
131
|
+
export type { FsFileStandalone } from "./FsFileStandalone.ts";
|
|
129
132
|
export type { FsError, FsResult, FsVoid } from "./FsError.ts";
|
package/src/pref/injectStyle.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Will remove the old style tag(s) if exist
|
|
5
5
|
*/
|
|
6
6
|
export function injectStyle(id: string, style: string) {
|
|
7
|
-
const styleTags = document.querySelectorAll(`style[data-inject="${id}"`);
|
|
7
|
+
const styleTags = document.querySelectorAll(`style[data-inject="${id}"]`);
|
|
8
8
|
if (styleTags.length !== 1) {
|
|
9
9
|
const styleTag = document.createElement("style");
|
|
10
10
|
styleTag.setAttribute("data-inject", id);
|