@jk3labs/paperclip-plugin-file-browser 0.2.4 → 0.2.6
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/README.md +19 -80
- package/dist/manifest.js +89 -0
- package/dist/manifest.js.map +7 -0
- package/dist/ui/index.js +198 -0
- package/dist/ui/index.js.map +7 -0
- package/dist/worker.js +9268 -0
- package/dist/worker.js.map +7 -0
- package/package.json +48 -34
- package/IMPLEMENTATION_NOTES.md +0 -0
- package/dist/__tests__/file-browser.spec.js +0 -140
- package/dist/index.js +0 -110
- package/index.js +0 -82
- package/jest.config.js +0 -6
- package/manifest.ts +0 -15
- package/src/__tests__/file-browser.spec.ts +0 -161
- package/src/index.ts +0 -128
- package/tsconfig.json +0 -15
package/README.md
CHANGED
|
@@ -1,91 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
# JKL File Browser
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A file browser plugin for Paperclip workspaces
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
1. Clone this repository:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
git clone https://github.com/jk3labs/paperclip-plugin-file-browser.git
|
|
11
|
-
cd paperclip-plugin-file-browser
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
2. Install dependencies:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
3. Compile the plugin:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npx tsc
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
4. Install the plugin in Paperclip:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
paperclipai plugin install ./paperclip-plugin-file-browser
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## API Endpoints
|
|
33
|
-
|
|
34
|
-
### 1. List Directory Contents
|
|
35
|
-
|
|
36
|
-
**Endpoint:** `GET /files/list`
|
|
37
|
-
|
|
38
|
-
**Query Parameters:**
|
|
39
|
-
- `path` (optional): Relative path to the directory (defaults to root).
|
|
5
|
+
## Development
|
|
40
6
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"currentPath": "relative/path",
|
|
47
|
-
"rootDir": "/absolute/root/path"
|
|
48
|
-
}
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm dev # watch builds
|
|
10
|
+
pnpm dev:ui # local dev server with hot-reload events
|
|
11
|
+
pnpm test
|
|
49
12
|
```
|
|
50
13
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### 2. Download File
|
|
57
|
-
|
|
58
|
-
**Endpoint:** `GET /files/download`
|
|
59
|
-
|
|
60
|
-
**Query Parameters:**
|
|
61
|
-
- `path` (required): Relative path to the file.
|
|
14
|
+
`pnpm dev` rebuilds the worker, manifest, and UI bundles into `dist/`.
|
|
15
|
+
When this package is installed from a local path, Paperclip watches that rebuilt
|
|
16
|
+
output and reloads the plugin worker. Local installs run trusted code from this
|
|
17
|
+
folder on your machine.
|
|
62
18
|
|
|
63
|
-
**Response:**
|
|
64
|
-
- Streams the file for download.
|
|
65
19
|
|
|
66
|
-
**Errors:**
|
|
67
|
-
- `400 Bad Request`: Path parameter missing.
|
|
68
|
-
- `403 Forbidden`: Access denied (path outside root).
|
|
69
|
-
- `404 Not Found`: File does not exist.
|
|
70
|
-
- `500 Internal Server Error`: Server error.
|
|
71
20
|
|
|
72
|
-
|
|
21
|
+
## Install Into Paperclip
|
|
73
22
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- `path` (required): Relative path to the directory.
|
|
78
|
-
|
|
79
|
-
**Response:**
|
|
80
|
-
- Streams the directory as a ZIP file.
|
|
81
|
-
|
|
82
|
-
**Errors:**
|
|
83
|
-
- `400 Bad Request`: Path parameter missing.
|
|
84
|
-
- `403 Forbidden`: Access denied (path outside root).
|
|
85
|
-
- `404 Not Found`: Directory does not exist.
|
|
86
|
-
- `500 Internal Server Error`: Server error.
|
|
23
|
+
```bash
|
|
24
|
+
paperclipai plugin install C:/Users/Jad_K/.paperclip/instances/default/projects/d0b61e38-82d9-4bcf-b9e8-8ea970388ba9/2ba5752a-ea82-4afc-8d60-2ede6be97f44/_default/file-browser
|
|
25
|
+
```
|
|
87
26
|
|
|
88
|
-
##
|
|
27
|
+
## Build Options
|
|
89
28
|
|
|
90
|
-
-
|
|
91
|
-
-
|
|
29
|
+
- `pnpm build` uses esbuild presets from `@jkawwa/paperclipai-plugin-sdk/bundlers`.
|
|
30
|
+
- `pnpm build:rollup` uses rollup presets from the same SDK.
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/manifest.ts
|
|
2
|
+
var manifest = {
|
|
3
|
+
id: "jkl.file-browser",
|
|
4
|
+
apiVersion: 1,
|
|
5
|
+
version: "0.2.0",
|
|
6
|
+
displayName: "File Browser",
|
|
7
|
+
description: "A file browser plugin for Paperclip workspaces",
|
|
8
|
+
author: "Plugin Author",
|
|
9
|
+
categories: ["workspace"],
|
|
10
|
+
capabilities: [
|
|
11
|
+
"events.subscribe",
|
|
12
|
+
"plugin.state.read",
|
|
13
|
+
"plugin.state.write",
|
|
14
|
+
"local.folders",
|
|
15
|
+
"ui.dashboardWidget.register",
|
|
16
|
+
"ui.detailTab.register",
|
|
17
|
+
"ui.sidebar.register",
|
|
18
|
+
"api.routes.register",
|
|
19
|
+
"project.workspaces.read"
|
|
20
|
+
],
|
|
21
|
+
localFolders: [
|
|
22
|
+
{
|
|
23
|
+
folderKey: "workspace",
|
|
24
|
+
displayName: "Workspace Root",
|
|
25
|
+
description: "Root directory of the project workspace to browse files",
|
|
26
|
+
access: "read"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
apiRoutes: [
|
|
30
|
+
{
|
|
31
|
+
routeKey: "list-files",
|
|
32
|
+
method: "GET",
|
|
33
|
+
path: "/files/list",
|
|
34
|
+
auth: "board-or-agent",
|
|
35
|
+
capability: "api.routes.register",
|
|
36
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
routeKey: "download-file",
|
|
40
|
+
method: "GET",
|
|
41
|
+
path: "/files/download",
|
|
42
|
+
auth: "board-or-agent",
|
|
43
|
+
capability: "api.routes.register",
|
|
44
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
routeKey: "download-zip",
|
|
48
|
+
method: "GET",
|
|
49
|
+
path: "/files/zip",
|
|
50
|
+
auth: "board-or-agent",
|
|
51
|
+
capability: "api.routes.register",
|
|
52
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
entrypoints: {
|
|
56
|
+
worker: "./dist/worker.js",
|
|
57
|
+
ui: "./dist/ui"
|
|
58
|
+
},
|
|
59
|
+
ui: {
|
|
60
|
+
slots: [
|
|
61
|
+
{
|
|
62
|
+
type: "dashboardWidget",
|
|
63
|
+
id: "file-browser-widget",
|
|
64
|
+
displayName: "File Browser",
|
|
65
|
+
exportName: "DashboardWidget"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "detailTab",
|
|
69
|
+
id: "file-browser-tab",
|
|
70
|
+
displayName: "Files",
|
|
71
|
+
exportName: "FileBrowserTab",
|
|
72
|
+
entityTypes: ["project", "issue"],
|
|
73
|
+
order: 50
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "projectSidebarItem",
|
|
77
|
+
id: "file-browser-sidebar",
|
|
78
|
+
displayName: "Files",
|
|
79
|
+
exportName: "FileBrowserSidebarItem",
|
|
80
|
+
entityTypes: ["project"]
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var manifest_default = manifest;
|
|
86
|
+
export {
|
|
87
|
+
manifest_default as default
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/manifest.ts"],
|
|
4
|
+
"sourcesContent": ["import type { PaperclipPluginManifestV1 } from \"@paperclipai/plugin-sdk\";\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: \"jkl.file-browser\",\n apiVersion: 1,\n version: \"0.2.0\",\n displayName: \"File Browser\",\n description: \"A file browser plugin for Paperclip workspaces\",\n author: \"Plugin Author\",\n categories: [\"workspace\"],\n capabilities: [\n \"events.subscribe\",\n \"plugin.state.read\",\n \"plugin.state.write\",\n \"local.folders\",\n \"ui.dashboardWidget.register\",\n \"ui.detailTab.register\",\n \"ui.sidebar.register\",\n \"api.routes.register\",\n \"project.workspaces.read\"\n ],\n localFolders: [\n {\n folderKey: \"workspace\",\n displayName: \"Workspace Root\",\n description: \"Root directory of the project workspace to browse files\",\n access: \"read\"\n }\n ],\n apiRoutes: [\n {\n routeKey: \"list-files\",\n method: \"GET\",\n path: \"/files/list\",\n auth: \"board-or-agent\",\n capability: \"api.routes.register\",\n companyResolution: { from: \"query\", key: \"companyId\" }\n },\n {\n routeKey: \"download-file\",\n method: \"GET\",\n path: \"/files/download\",\n auth: \"board-or-agent\",\n capability: \"api.routes.register\",\n companyResolution: { from: \"query\", key: \"companyId\" }\n },\n {\n routeKey: \"download-zip\",\n method: \"GET\",\n path: \"/files/zip\",\n auth: \"board-or-agent\",\n capability: \"api.routes.register\",\n companyResolution: { from: \"query\", key: \"companyId\" }\n }\n ],\n entrypoints: {\n worker: \"./dist/worker.js\",\n ui: \"./dist/ui\"\n },\n ui: {\n slots: [\n {\n type: \"dashboardWidget\",\n id: \"file-browser-widget\",\n displayName: \"File Browser\",\n exportName: \"DashboardWidget\"\n },\n {\n type: \"detailTab\",\n id: \"file-browser-tab\",\n displayName: \"Files\",\n exportName: \"FileBrowserTab\",\n entityTypes: [\"project\", \"issue\"],\n order: 50\n },\n {\n type: \"projectSidebarItem\",\n id: \"file-browser-sidebar\",\n displayName: \"Files\",\n exportName: \"FileBrowserSidebarItem\",\n entityTypes: [\"project\"]\n }\n ]\n }\n};\n\nexport default manifest;\n"],
|
|
5
|
+
"mappings": ";AAEA,IAAM,WAAsC;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY,CAAC,WAAW;AAAA,EACxB,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,MACE,WAAW;AAAA,MACX,aAAa;AAAA,MACb,aAAa;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,WAAW;AAAA,IACT;AAAA,MACE,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,mBAAmB,EAAE,MAAM,SAAS,KAAK,YAAY;AAAA,IACvD;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,mBAAmB,EAAE,MAAM,SAAS,KAAK,YAAY;AAAA,IACvD;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,mBAAmB,EAAE,MAAM,SAAS,KAAK,YAAY;AAAA,IACvD;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,IAAI;AAAA,EACN;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,aAAa,CAAC,WAAW,OAAO;AAAA,QAChC,OAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,aAAa,CAAC,SAAS;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// src/ui/index.tsx
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
usePluginData,
|
|
5
|
+
usePluginAction,
|
|
6
|
+
useHostContext,
|
|
7
|
+
useHostNavigation,
|
|
8
|
+
usePluginBridgeReady
|
|
9
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
10
|
+
import {
|
|
11
|
+
FileTree,
|
|
12
|
+
MetricCard,
|
|
13
|
+
Spinner,
|
|
14
|
+
ErrorBoundary
|
|
15
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
function buildNodes(data) {
|
|
18
|
+
if (!data) return [];
|
|
19
|
+
const dirNodes = data.directories.map((d) => ({
|
|
20
|
+
name: d.name,
|
|
21
|
+
path: d.path,
|
|
22
|
+
kind: "dir",
|
|
23
|
+
children: []
|
|
24
|
+
}));
|
|
25
|
+
const fileNodes = data.files.map((f) => ({
|
|
26
|
+
name: f.name,
|
|
27
|
+
path: f.path,
|
|
28
|
+
kind: "file",
|
|
29
|
+
children: []
|
|
30
|
+
}));
|
|
31
|
+
return [...dirNodes, ...fileNodes];
|
|
32
|
+
}
|
|
33
|
+
function formatSize(bytes) {
|
|
34
|
+
if (bytes == null) return "-";
|
|
35
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
36
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
37
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
38
|
+
}
|
|
39
|
+
function FileBrowser({ companyId }) {
|
|
40
|
+
console.debug("[FileBrowser] Initializing with companyId:", companyId);
|
|
41
|
+
const [currentPath, setCurrentPath] = useState("");
|
|
42
|
+
const [selectedFile, setSelectedFile] = useState(null);
|
|
43
|
+
console.debug("[FileBrowser] State initialized. Fetching data for path:", currentPath);
|
|
44
|
+
const { data, loading, error, refresh } = usePluginData(
|
|
45
|
+
"file-tree",
|
|
46
|
+
{ companyId: companyId ?? "", relativePath: currentPath }
|
|
47
|
+
);
|
|
48
|
+
console.debug("[FileBrowser] usePluginData result:", { data, loading, error });
|
|
49
|
+
const downloadFile = usePluginAction("download-file");
|
|
50
|
+
const downloadZip = usePluginAction("download-zip");
|
|
51
|
+
console.debug("[FileBrowser] Plugin actions initialized:", { downloadFile, downloadZip });
|
|
52
|
+
const nodes = buildNodes(data);
|
|
53
|
+
const handleSelectFile = useCallback((path) => {
|
|
54
|
+
setSelectedFile(path);
|
|
55
|
+
}, []);
|
|
56
|
+
const handleToggleDir = useCallback((path) => {
|
|
57
|
+
setCurrentPath(path);
|
|
58
|
+
setSelectedFile(null);
|
|
59
|
+
}, []);
|
|
60
|
+
const handleDownloadFile = useCallback(async () => {
|
|
61
|
+
console.debug("[FileBrowser] handleDownloadFile called with:", { selectedFile, companyId });
|
|
62
|
+
if (!selectedFile || !companyId) {
|
|
63
|
+
console.debug("[FileBrowser] handleDownloadFile: Missing selectedFile or companyId");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const result = await downloadFile({ companyId, filePath: selectedFile });
|
|
68
|
+
console.debug("[FileBrowser] handleDownloadFile result:", result);
|
|
69
|
+
if (result?.url) {
|
|
70
|
+
window.open(result.url, "_blank");
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error("[FileBrowser] Failed to download file:", err);
|
|
74
|
+
}
|
|
75
|
+
}, [selectedFile, companyId, downloadFile]);
|
|
76
|
+
const handleDownloadZip = useCallback(async () => {
|
|
77
|
+
console.debug("[FileBrowser] handleDownloadZip called with:", { currentPath, companyId });
|
|
78
|
+
if (!currentPath || !companyId) {
|
|
79
|
+
console.debug("[FileBrowser] handleDownloadZip: Missing currentPath or companyId");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const result = await downloadZip({ companyId, directoryPath: currentPath });
|
|
84
|
+
console.debug("[FileBrowser] handleDownloadZip result:", result);
|
|
85
|
+
if (result?.url) {
|
|
86
|
+
window.open(result.url, "_blank");
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error("[FileBrowser] Failed to download zip:", err);
|
|
90
|
+
}
|
|
91
|
+
}, [currentPath, companyId, downloadZip]);
|
|
92
|
+
const handleNavigateUp = useCallback(() => {
|
|
93
|
+
if (!currentPath) return;
|
|
94
|
+
const parts = currentPath.split("/");
|
|
95
|
+
parts.pop();
|
|
96
|
+
setCurrentPath(parts.join("/"));
|
|
97
|
+
setSelectedFile(null);
|
|
98
|
+
}, [currentPath]);
|
|
99
|
+
const handleRefresh = useCallback(() => {
|
|
100
|
+
refresh();
|
|
101
|
+
}, [refresh]);
|
|
102
|
+
const fileCount = data?.files.length ?? 0;
|
|
103
|
+
const dirCount = data?.directories.length ?? 0;
|
|
104
|
+
const totalSize = data?.files.reduce((sum, f) => sum + (f.size ?? 0), 0) ?? 0;
|
|
105
|
+
if (!companyId) {
|
|
106
|
+
return /* @__PURE__ */ jsx("div", { style: { padding: "1rem" }, children: "Select a company to browse files." });
|
|
107
|
+
}
|
|
108
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", padding: "0.5rem 0" }, children: [
|
|
109
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", flexWrap: "wrap" }, children: [
|
|
110
|
+
/* @__PURE__ */ jsx(MetricCard, { label: "Files", value: fileCount }),
|
|
111
|
+
/* @__PURE__ */ jsx(MetricCard, { label: "Directories", value: dirCount }),
|
|
112
|
+
/* @__PURE__ */ jsx(MetricCard, { label: "Total Size", value: formatSize(totalSize) })
|
|
113
|
+
] }),
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", alignItems: "center", flexWrap: "wrap" }, children: [
|
|
115
|
+
currentPath && /* @__PURE__ */ jsx("button", { onClick: handleNavigateUp, style: { fontSize: "0.85rem" }, children: ".. /" }),
|
|
116
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: "0.85rem", color: "var(--text-secondary, #888)" }, children: [
|
|
117
|
+
"/",
|
|
118
|
+
currentPath || ""
|
|
119
|
+
] }),
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { style: { marginLeft: "auto", display: "flex", gap: "0.5rem" }, children: [
|
|
121
|
+
/* @__PURE__ */ jsx("button", { onClick: handleRefresh, style: { fontSize: "0.85rem" }, children: "Refresh" }),
|
|
122
|
+
selectedFile && /* @__PURE__ */ jsx("button", { onClick: handleDownloadFile, style: { fontSize: "0.85rem" }, children: "Download File" }),
|
|
123
|
+
currentPath && /* @__PURE__ */ jsx("button", { onClick: handleDownloadZip, style: { fontSize: "0.85rem" }, children: "Download Folder (ZIP)" })
|
|
124
|
+
] })
|
|
125
|
+
] }),
|
|
126
|
+
data?.error && /* @__PURE__ */ jsxs("div", { style: { color: "var(--text-error, red)", fontSize: "0.85rem" }, children: [
|
|
127
|
+
"Error: ",
|
|
128
|
+
data.error
|
|
129
|
+
] }),
|
|
130
|
+
loading && /* @__PURE__ */ jsx(Spinner, { size: "medium", text: "Loading files...", style: { padding: "2rem 0" } }),
|
|
131
|
+
!loading && /* @__PURE__ */ jsx(
|
|
132
|
+
FileTree,
|
|
133
|
+
{
|
|
134
|
+
nodes,
|
|
135
|
+
selectedFile,
|
|
136
|
+
expandedPaths: [],
|
|
137
|
+
onSelectFile: handleSelectFile,
|
|
138
|
+
onToggleDir: handleToggleDir,
|
|
139
|
+
loading,
|
|
140
|
+
error: error ? { message: error.message } : null,
|
|
141
|
+
empty: { title: "Empty directory", description: "No files or directories found." },
|
|
142
|
+
ariaLabel: "File browser"
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
selectedFile && /* @__PURE__ */ jsxs("div", { style: { fontSize: "0.8rem", color: "var(--text-secondary, #888)", paddingTop: "0.25rem" }, children: [
|
|
146
|
+
"Selected: ",
|
|
147
|
+
selectedFile
|
|
148
|
+
] })
|
|
149
|
+
] });
|
|
150
|
+
}
|
|
151
|
+
function DashboardWidget(_props) {
|
|
152
|
+
console.debug("[DashboardWidget] Rendering with props:", _props);
|
|
153
|
+
const { companyId } = useHostContext();
|
|
154
|
+
console.debug("[DashboardWidget] Resolved companyId:", companyId);
|
|
155
|
+
const isBridgeReady = usePluginBridgeReady();
|
|
156
|
+
console.debug("[DashboardWidget] Plugin bridge ready:", isBridgeReady);
|
|
157
|
+
if (!isBridgeReady) {
|
|
158
|
+
return /* @__PURE__ */ jsx(Spinner, { size: "small", text: "Initializing plugin..." });
|
|
159
|
+
}
|
|
160
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, { fallback: /* @__PURE__ */ jsx("div", { style: { padding: "1rem", color: "var(--text-error, red)" }, children: "File Browser: failed to render" }), children: /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.5rem" }, children: [
|
|
161
|
+
/* @__PURE__ */ jsx("strong", { children: "File Browser" }),
|
|
162
|
+
/* @__PURE__ */ jsx(FileBrowser, { companyId })
|
|
163
|
+
] }) });
|
|
164
|
+
}
|
|
165
|
+
function FileBrowserTab(props) {
|
|
166
|
+
console.debug("[FileBrowserTab] Rendering with props:", props);
|
|
167
|
+
const { companyId } = props.context;
|
|
168
|
+
console.debug("[FileBrowserTab] Resolved companyId from context:", companyId);
|
|
169
|
+
const isBridgeReady = usePluginBridgeReady();
|
|
170
|
+
console.debug("[FileBrowserTab] Plugin bridge ready:", isBridgeReady);
|
|
171
|
+
if (!isBridgeReady) {
|
|
172
|
+
return /* @__PURE__ */ jsx(Spinner, { size: "small", text: "Initializing plugin..." });
|
|
173
|
+
}
|
|
174
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, { fallback: /* @__PURE__ */ jsx("div", { style: { padding: "1rem", color: "var(--text-error, red)" }, children: "File Browser tab: failed to render" }), children: /* @__PURE__ */ jsx(FileBrowser, { companyId }) });
|
|
175
|
+
}
|
|
176
|
+
function FileBrowserSidebarItem(props) {
|
|
177
|
+
const { entityId: projectId } = props.context;
|
|
178
|
+
const { navigate, resolveHref } = useHostNavigation();
|
|
179
|
+
const href = resolveHref(`/projects/${projectId}?tab=plugin:jkl.file-browser:file-browser-tab`);
|
|
180
|
+
return /* @__PURE__ */ jsx(
|
|
181
|
+
"a",
|
|
182
|
+
{
|
|
183
|
+
href,
|
|
184
|
+
onClick: (e) => {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
navigate(href);
|
|
187
|
+
},
|
|
188
|
+
style: { fontSize: "0.85rem", display: "flex", alignItems: "center", gap: "0.35rem", padding: "0.2rem 0", color: "var(--text-link, #4488cc)", textDecoration: "none" },
|
|
189
|
+
children: "Files"
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
DashboardWidget,
|
|
195
|
+
FileBrowserSidebarItem,
|
|
196
|
+
FileBrowserTab
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/index.tsx"],
|
|
4
|
+
"sourcesContent": ["import { useCallback, useState } from \"react\";\nimport {\n usePluginData,\n usePluginAction,\n useHostContext,\n useHostNavigation,\n usePluginBridgeReady,\n type PluginWidgetProps,\n type PluginDetailTabProps,\n type PluginProjectSidebarItemProps,\n} from \"@paperclipai/plugin-sdk/ui\";\nimport {\n FileTree,\n MetricCard,\n Spinner,\n ErrorBoundary,\n type FileTreeNode,\n} from \"@paperclipai/plugin-sdk/ui\";\n\ninterface FileEntry {\n name: string;\n path: string;\n kind: \"file\" | \"directory\";\n size?: number | null;\n modifiedAt?: string | null;\n}\n\ninterface FileTreeData {\n currentPath: string;\n directories: FileEntry[];\n files: FileEntry[];\n error?: string;\n}\n\nfunction buildNodes(data: FileTreeData | null): FileTreeNode[] {\n if (!data) return [];\n const dirNodes: FileTreeNode[] = data.directories.map((d) => ({\n name: d.name,\n path: d.path,\n kind: \"dir\" as const,\n children: [],\n }));\n const fileNodes: FileTreeNode[] = data.files.map((f) => ({\n name: f.name,\n path: f.path,\n kind: \"file\" as const,\n children: [],\n }));\n return [...dirNodes, ...fileNodes];\n}\n\nfunction formatSize(bytes: number | null | undefined): string {\n if (bytes == null) return \"-\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction FileBrowser({ companyId }: { companyId: string | null }) {\n console.debug(\"[FileBrowser] Initializing with companyId:\", companyId);\n const [currentPath, setCurrentPath] = useState(\"\");\n const [selectedFile, setSelectedFile] = useState<string | null>(null);\n console.debug(\"[FileBrowser] State initialized. Fetching data for path:\", currentPath);\n\n const { data, loading, error, refresh } = usePluginData<FileTreeData>(\n \"file-tree\",\n { companyId: companyId ?? \"\", relativePath: currentPath }\n );\n console.debug(\"[FileBrowser] usePluginData result:\", { data, loading, error });\n\n const downloadFile = usePluginAction(\"download-file\");\n const downloadZip = usePluginAction(\"download-zip\");\n console.debug(\"[FileBrowser] Plugin actions initialized:\", { downloadFile, downloadZip });\n\n const nodes = buildNodes(data);\n\n const handleSelectFile = useCallback((path: string) => {\n setSelectedFile(path);\n }, []);\n\n const handleToggleDir = useCallback((path: string) => {\n setCurrentPath(path);\n setSelectedFile(null);\n }, []);\n\n const handleDownloadFile = useCallback(async () => {\n console.debug(\"[FileBrowser] handleDownloadFile called with:\", { selectedFile, companyId });\n if (!selectedFile || !companyId) {\n console.debug(\"[FileBrowser] handleDownloadFile: Missing selectedFile or companyId\");\n return;\n }\n try {\n const result = await downloadFile({ companyId, filePath: selectedFile });\n console.debug(\"[FileBrowser] handleDownloadFile result:\", result);\n if (result?.url) {\n window.open(result.url, \"_blank\");\n }\n } catch (err) {\n console.error(\"[FileBrowser] Failed to download file:\", err);\n }\n }, [selectedFile, companyId, downloadFile]);\n\n const handleDownloadZip = useCallback(async () => {\n console.debug(\"[FileBrowser] handleDownloadZip called with:\", { currentPath, companyId });\n if (!currentPath || !companyId) {\n console.debug(\"[FileBrowser] handleDownloadZip: Missing currentPath or companyId\");\n return;\n }\n try {\n const result = await downloadZip({ companyId, directoryPath: currentPath });\n console.debug(\"[FileBrowser] handleDownloadZip result:\", result);\n if (result?.url) {\n window.open(result.url, \"_blank\");\n }\n } catch (err) {\n console.error(\"[FileBrowser] Failed to download zip:\", err);\n }\n }, [currentPath, companyId, downloadZip]);\n\n const handleNavigateUp = useCallback(() => {\n if (!currentPath) return;\n const parts = currentPath.split(\"/\");\n parts.pop();\n setCurrentPath(parts.join(\"/\"));\n setSelectedFile(null);\n }, [currentPath]);\n\n const handleRefresh = useCallback(() => {\n refresh();\n }, [refresh]);\n\n const fileCount = data?.files.length ?? 0;\n const dirCount = data?.directories.length ?? 0;\n const totalSize = data?.files.reduce((sum, f) => sum + (f.size ?? 0), 0) ?? 0;\n\n if (!companyId) {\n return <div style={{ padding: \"1rem\" }}>Select a company to browse files.</div>;\n }\n\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.75rem\", padding: \"0.5rem 0\" }}>\n <div style={{ display: \"flex\", gap: \"0.5rem\", flexWrap: \"wrap\" }}>\n <MetricCard label=\"Files\" value={fileCount} />\n <MetricCard label=\"Directories\" value={dirCount} />\n <MetricCard label=\"Total Size\" value={formatSize(totalSize)} />\n </div>\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", alignItems: \"center\", flexWrap: \"wrap\" }}>\n {currentPath && (\n <button onClick={handleNavigateUp} style={{ fontSize: \"0.85rem\" }}>\n .. / \n </button>\n )}\n <span style={{ fontSize: \"0.85rem\", color: \"var(--text-secondary, #888)\" }}>\n /{currentPath || \"\"}\n </span>\n <div style={{ marginLeft: \"auto\", display: \"flex\", gap: \"0.5rem\" }}>\n <button onClick={handleRefresh} style={{ fontSize: \"0.85rem\" }}>\n Refresh\n </button>\n {selectedFile && (\n <button onClick={handleDownloadFile} style={{ fontSize: \"0.85rem\" }}>\n Download File\n </button>\n )}\n {currentPath && (\n <button onClick={handleDownloadZip} style={{ fontSize: \"0.85rem\" }}>\n Download Folder (ZIP)\n </button>\n )}\n </div>\n </div>\n\n {data?.error && (\n <div style={{ color: \"var(--text-error, red)\", fontSize: \"0.85rem\" }}>\n Error: {data.error}\n </div>\n )}\n \n {loading && (\n <Spinner size=\"medium\" text=\"Loading files...\" style={{ padding: \"2rem 0\" }} />\n )}\n \n {!loading && (\n <FileTree\n nodes={nodes}\n selectedFile={selectedFile}\n expandedPaths={[]}\n onSelectFile={handleSelectFile}\n onToggleDir={handleToggleDir}\n loading={loading}\n error={error ? { message: error.message } : null}\n empty={{ title: \"Empty directory\", description: \"No files or directories found.\" }}\n ariaLabel=\"File browser\"\n />\n )}\n\n {selectedFile && (\n <div style={{ fontSize: \"0.8rem\", color: \"var(--text-secondary, #888)\", paddingTop: \"0.25rem\" }}>\n Selected: {selectedFile}\n </div>\n )}\n </div>\n );\n}\n\nexport function DashboardWidget(_props: PluginWidgetProps) {\n console.debug(\"[DashboardWidget] Rendering with props:\", _props);\n const { companyId } = useHostContext();\n console.debug(\"[DashboardWidget] Resolved companyId:\", companyId);\n const isBridgeReady = usePluginBridgeReady();\n console.debug(\"[DashboardWidget] Plugin bridge ready:\", isBridgeReady);\n\n if (!isBridgeReady) {\n return <Spinner size=\"small\" text=\"Initializing plugin...\" />;\n }\n\n return (\n <ErrorBoundary fallback={<div style={{ padding: \"1rem\", color: \"var(--text-error, red)\" }}>File Browser: failed to render</div>}>\n <div style={{ display: \"grid\", gap: \"0.5rem\" }}>\n <strong>File Browser</strong>\n <FileBrowser companyId={companyId} />\n </div>\n </ErrorBoundary>\n );\n}\n\nexport function FileBrowserTab(props: PluginDetailTabProps) {\n console.debug(\"[FileBrowserTab] Rendering with props:\", props);\n const { companyId } = props.context;\n console.debug(\"[FileBrowserTab] Resolved companyId from context:\", companyId);\n const isBridgeReady = usePluginBridgeReady();\n console.debug(\"[FileBrowserTab] Plugin bridge ready:\", isBridgeReady);\n\n if (!isBridgeReady) {\n return <Spinner size=\"small\" text=\"Initializing plugin...\" />;\n }\n\n return (\n <ErrorBoundary fallback={<div style={{ padding: \"1rem\", color: \"var(--text-error, red)\" }}>File Browser tab: failed to render</div>}>\n <FileBrowser companyId={companyId} />\n </ErrorBoundary>\n );\n}\n\nexport function FileBrowserSidebarItem(props: PluginProjectSidebarItemProps) {\n const { entityId: projectId } = props.context;\n const { navigate, resolveHref } = useHostNavigation();\n const href = resolveHref(`/projects/${projectId}?tab=plugin:jkl.file-browser:file-browser-tab`);\n return (\n <a\n href={href}\n onClick={(e) => { e.preventDefault(); navigate(href); }}\n style={{ fontSize: \"0.85rem\", display: \"flex\", alignItems: \"center\", gap: \"0.35rem\", padding: \"0.2rem 0\", color: \"var(--text-link, #4488cc)\", textDecoration: \"none\" }}\n >\n Files\n </a>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|