@ziioapp/finder-opfs 0.1.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/LICENSE +22 -0
- package/README.md +18 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +137 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ziioai
|
|
4
|
+
Copyright (c) 2023 shadcn
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @ziioapp/finder-opfs
|
|
2
|
+
|
|
3
|
+
把 `@ziioapp/os-features` 的 ZiioApp OPFS 应用数据目录接入 `@ziioapp/finder`。这是可选适配包;Finder 核心不依赖它。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createOpfsFinderBackend } from "@ziioapp/finder-opfs";
|
|
7
|
+
|
|
8
|
+
const opfsBackend = createOpfsFinderBackend({ appId: "unia" });
|
|
9
|
+
await opfsBackend.ensureLayout();
|
|
10
|
+
|
|
11
|
+
// <FinderDesktop backend={opfsBackend} api={finderStore} />
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`appId` 决定文件根目录,即 `getZiioOpfsAppPaths(appId).appData`;可选 `userId` 会同时传给目录布局初始化。也可以通过 `id`、`label` 覆盖 Finder 中展示的后端标识和名称。调用 `ensureLayout()` 后再展示或操作文件。
|
|
15
|
+
|
|
16
|
+
文件路径对 Finder 保持无前导斜杠的相对形式,适配器负责与 OPFS 绝对路径转换。它沿用原 `unia` 和 `demo-paper-smasher` 实现的读写、重命名及不支持原生 `move()` 时的复制回退逻辑。浏览器需要支持 OPFS;不同 origin/存储分区不会因为安装同一个包而自动共享文件。
|
|
17
|
+
|
|
18
|
+
该包不适配 `opfs-tools` API;使用它的应用可以继续提供自己的 `FsBackend`。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FsBackend } from "@ziioapp/finder/backend";
|
|
2
|
+
export interface CreateOpfsFinderBackendOptions {
|
|
3
|
+
appId: string;
|
|
4
|
+
userId?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface OpfsFinderBackend extends FsBackend {
|
|
9
|
+
/** Initialize the ZiioApp directory layout before first use. */
|
|
10
|
+
ensureLayout(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/** Create an OPFS-backed Finder instance scoped to one ZiioApp app directory. */
|
|
13
|
+
export declare function createOpfsFinderBackend(options: CreateOpfsFinderBackendOptions): OpfsFinderBackend;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { guessContentType } from "@ziioapp/finder/format";
|
|
2
|
+
import { ensureZiioOpfsLayout, getOpfsDirectoryHandle, getZiioOpfsAppPaths, joinRelativeOpfsPath, listOpfsDirectory, opfsBasename, opfsDirectoryExists, opfsDirname, opfsFileExists, readOpfsFile, removeOpfsEntry, stripOpfsPrefix, writeOpfsFile, } from "@ziioapp/os-features/opfs";
|
|
3
|
+
const invalidEntryNameRe = /[/\\\0]/;
|
|
4
|
+
function assertEntryName(name) {
|
|
5
|
+
if (!name || name === "." || name === ".." || invalidEntryNameRe.test(name)) {
|
|
6
|
+
throw new Error("名称不能为空,也不能包含路径分隔符。");
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
async function entryExists(path) {
|
|
10
|
+
const [fileExists, directoryExists] = await Promise.all([
|
|
11
|
+
opfsFileExists(path),
|
|
12
|
+
opfsDirectoryExists(path),
|
|
13
|
+
]);
|
|
14
|
+
return fileExists || directoryExists;
|
|
15
|
+
}
|
|
16
|
+
async function copyEntry(sourcePath, targetPath) {
|
|
17
|
+
if (await opfsDirectoryExists(sourcePath)) {
|
|
18
|
+
await getOpfsDirectoryHandle(targetPath, { create: true });
|
|
19
|
+
const entries = await listOpfsDirectory(sourcePath);
|
|
20
|
+
await Promise.all(entries.map((entry) => copyEntry(entry.path, joinRelativeOpfsPath(targetPath, entry.name))));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await writeOpfsFile(targetPath, await readOpfsFile(sourcePath));
|
|
24
|
+
}
|
|
25
|
+
/** Create an OPFS-backed Finder instance scoped to one ZiioApp app directory. */
|
|
26
|
+
export function createOpfsFinderBackend(options) {
|
|
27
|
+
const { appId, userId, id = "opfs", label = "本地文件 (OPFS)" } = options;
|
|
28
|
+
const filesRoot = getZiioOpfsAppPaths(appId, { userId }).appData;
|
|
29
|
+
function resolvePath(visiblePath) {
|
|
30
|
+
return joinRelativeOpfsPath(filesRoot, visiblePath);
|
|
31
|
+
}
|
|
32
|
+
function toVisiblePath(path) {
|
|
33
|
+
return stripOpfsPrefix(path, filesRoot);
|
|
34
|
+
}
|
|
35
|
+
async function moveEntry(item, currentDir, newName) {
|
|
36
|
+
assertEntryName(newName);
|
|
37
|
+
const sourcePath = resolvePath(item.path);
|
|
38
|
+
const targetPath = resolvePath(joinRelativeOpfsPath(currentDir, newName));
|
|
39
|
+
if (await entryExists(targetPath)) {
|
|
40
|
+
throw new Error(`“${newName}”已经存在。`);
|
|
41
|
+
}
|
|
42
|
+
const sourceParent = await getOpfsDirectoryHandle(opfsDirname(sourcePath));
|
|
43
|
+
const sourceName = opfsBasename(sourcePath);
|
|
44
|
+
const sourceHandle = item.isDirectory
|
|
45
|
+
? await sourceParent.getDirectoryHandle(sourceName)
|
|
46
|
+
: await sourceParent.getFileHandle(sourceName);
|
|
47
|
+
const move = sourceHandle.move;
|
|
48
|
+
if (typeof move === "function") {
|
|
49
|
+
const targetParent = await getOpfsDirectoryHandle(resolvePath(currentDir));
|
|
50
|
+
await move.call(sourceHandle, targetParent, newName);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await copyEntry(sourcePath, targetPath);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await removeOpfsEntry(targetPath, { recursive: true }).catch(() => {
|
|
58
|
+
// Preserve the original copy error if cleanup also fails.
|
|
59
|
+
});
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
await removeOpfsEntry(sourcePath, { recursive: item.isDirectory });
|
|
63
|
+
}
|
|
64
|
+
function toDriveItem(entry) {
|
|
65
|
+
return {
|
|
66
|
+
name: entry.name,
|
|
67
|
+
path: toVisiblePath(entry.path),
|
|
68
|
+
size: entry.size,
|
|
69
|
+
contentType: entry.type || guessContentType(entry.name),
|
|
70
|
+
modified: entry.lastModified
|
|
71
|
+
? new Date(entry.lastModified).toLocaleString()
|
|
72
|
+
: "",
|
|
73
|
+
isDirectory: entry.kind === "directory",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
label,
|
|
79
|
+
capabilities: {
|
|
80
|
+
upload: true,
|
|
81
|
+
mkdir: true,
|
|
82
|
+
rename: true,
|
|
83
|
+
remove: true,
|
|
84
|
+
cache: false,
|
|
85
|
+
},
|
|
86
|
+
async ensureLayout() {
|
|
87
|
+
await ensureZiioOpfsLayout({ appIds: [appId], userId });
|
|
88
|
+
},
|
|
89
|
+
initialPath() {
|
|
90
|
+
return "";
|
|
91
|
+
},
|
|
92
|
+
async list(path) {
|
|
93
|
+
const resolvedPath = resolvePath(path);
|
|
94
|
+
if (!(await opfsDirectoryExists(resolvedPath))) {
|
|
95
|
+
return { items: [], fromCache: false };
|
|
96
|
+
}
|
|
97
|
+
const entries = await listOpfsDirectory(resolvedPath);
|
|
98
|
+
return { items: entries.map(toDriveItem), fromCache: false };
|
|
99
|
+
},
|
|
100
|
+
async readBlob(item) {
|
|
101
|
+
return {
|
|
102
|
+
blob: await readOpfsFile(resolvePath(item.path)),
|
|
103
|
+
fromCache: false,
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
async upload(dirPath, files) {
|
|
107
|
+
for (const file of files)
|
|
108
|
+
assertEntryName(file.name);
|
|
109
|
+
await Promise.all(files.map((file) => writeOpfsFile(resolvePath(joinRelativeOpfsPath(dirPath, file.name)), file)));
|
|
110
|
+
},
|
|
111
|
+
async mkdir(dirPath, name) {
|
|
112
|
+
assertEntryName(name);
|
|
113
|
+
const path = resolvePath(joinRelativeOpfsPath(dirPath, name));
|
|
114
|
+
if (await entryExists(path)) {
|
|
115
|
+
throw new Error(`“${name}”已经存在。`);
|
|
116
|
+
}
|
|
117
|
+
await getOpfsDirectoryHandle(path, { create: true });
|
|
118
|
+
},
|
|
119
|
+
rename(item, currentDir, newName) {
|
|
120
|
+
return moveEntry(item, currentDir, newName);
|
|
121
|
+
},
|
|
122
|
+
async remove(item) {
|
|
123
|
+
await removeOpfsEntry(resolvePath(item.path), {
|
|
124
|
+
recursive: item.isDirectory,
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
canWriteDir() {
|
|
128
|
+
return true;
|
|
129
|
+
},
|
|
130
|
+
canWriteItem() {
|
|
131
|
+
return true;
|
|
132
|
+
},
|
|
133
|
+
notice() {
|
|
134
|
+
return null;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ziioapp/finder-opfs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ZiioApp OPFS backend adapter for @ziioapp/finder",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@ziioapp/finder": "^0.1.0",
|
|
15
|
+
"@ziioapp/os-features": "^0.1.0",
|
|
16
|
+
"typescript": "6.0.3"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@ziioapp/finder": "^0.1.0",
|
|
20
|
+
"@ziioapp/os-features": "^0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"registry": "https://registry.npmjs.org/"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": "ziioai",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/ziioai/ziioos.git",
|
|
36
|
+
"directory": "packages/finder-opfs"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/ziioai/ziioos#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/ziioai/ziioos/issues"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"finder",
|
|
44
|
+
"opfs",
|
|
45
|
+
"browser",
|
|
46
|
+
"ziioapp"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
50
|
+
"build": "node ../../scripts/build-package.mjs"
|
|
51
|
+
}
|
|
52
|
+
}
|