@jk3labs/paperclip-plugin-file-browser 0.1.5 → 0.2.1

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/dist/manifest.js CHANGED
@@ -2,7 +2,7 @@
2
2
  var manifest = {
3
3
  id: "jkl.file-browser",
4
4
  apiVersion: 1,
5
- version: "0.1.5",
5
+ version: "0.2.0",
6
6
  displayName: "File Browser",
7
7
  description: "A file browser plugin for Paperclip workspaces",
8
8
  author: "Plugin Author",
@@ -14,6 +14,7 @@ var manifest = {
14
14
  "local.folders",
15
15
  "ui.dashboardWidget.register",
16
16
  "ui.detailTab.register",
17
+ "ui.sidebar.register",
17
18
  "api.routes.register",
18
19
  "project.workspaces.read"
19
20
  ],
@@ -70,6 +71,13 @@ var manifest = {
70
71
  exportName: "FileBrowserTab",
71
72
  entityTypes: ["project", "issue"],
72
73
  order: 50
74
+ },
75
+ {
76
+ type: "projectSidebarItem",
77
+ id: "file-browser-sidebar",
78
+ displayName: "Files",
79
+ exportName: "FileBrowserSidebarItem",
80
+ entityTypes: ["project"]
73
81
  }
74
82
  ]
75
83
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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.1.5\",\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 \"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 }\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,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,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
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
6
  "names": []
7
7
  }
package/dist/ui/index.js CHANGED
@@ -3,11 +3,13 @@ import { useCallback, useState } from "react";
3
3
  import {
4
4
  usePluginData,
5
5
  usePluginAction,
6
- useHostContext
6
+ useHostContext,
7
+ useHostNavigation
7
8
  } from "@paperclipai/plugin-sdk/ui";
8
9
  import {
9
10
  FileTree,
10
- MetricCard
11
+ MetricCard,
12
+ ErrorBoundary
11
13
  } from "@paperclipai/plugin-sdk/ui";
12
14
  import { jsx, jsxs } from "react/jsx-runtime";
13
15
  function buildNodes(data) {
@@ -123,17 +125,35 @@ function FileBrowser({ companyId }) {
123
125
  }
124
126
  function DashboardWidget(_props) {
125
127
  const { companyId } = useHostContext();
126
- return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.5rem" }, children: [
128
+ 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: [
127
129
  /* @__PURE__ */ jsx("strong", { children: "File Browser" }),
128
130
  /* @__PURE__ */ jsx(FileBrowser, { companyId })
129
- ] });
131
+ ] }) });
130
132
  }
131
133
  function FileBrowserTab(props) {
132
134
  const { companyId } = props.context;
133
- return /* @__PURE__ */ jsx(FileBrowser, { companyId });
135
+ 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 }) });
136
+ }
137
+ function FileBrowserSidebarItem(props) {
138
+ const { entityId: projectId } = props.context;
139
+ const { navigate, resolveHref } = useHostNavigation();
140
+ const href = resolveHref(`/projects/${projectId}?tab=plugin:jkl.file-browser:file-browser-tab`);
141
+ return /* @__PURE__ */ jsx(
142
+ "a",
143
+ {
144
+ href,
145
+ onClick: (e) => {
146
+ e.preventDefault();
147
+ navigate(href);
148
+ },
149
+ style: { fontSize: "0.85rem", display: "flex", alignItems: "center", gap: "0.35rem", padding: "0.2rem 0", color: "var(--text-link, #4488cc)", textDecoration: "none" },
150
+ children: "Files"
151
+ }
152
+ );
134
153
  }
135
154
  export {
136
155
  DashboardWidget,
156
+ FileBrowserSidebarItem,
137
157
  FileBrowserTab
138
158
  };
139
159
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/index.tsx"],
4
- "sourcesContent": ["import { useCallback, useState } from \"react\";\nimport {\n usePluginData,\n usePluginAction,\n useHostContext,\n type PluginWidgetProps,\n type PluginDetailTabProps,\n} from \"@paperclipai/plugin-sdk/ui\";\nimport {\n FileTree,\n MetricCard,\n Spinner,\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 const [currentPath, setCurrentPath] = useState(\"\");\n const [selectedFile, setSelectedFile] = useState<string | null>(null);\n\n const { data, loading, error, refresh } = usePluginData<FileTreeData>(\n \"file-tree\",\n { companyId: companyId ?? \"\", relativePath: currentPath }\n );\n\n const downloadFile = usePluginAction(\"download-file\");\n const downloadZip = usePluginAction(\"download-zip\");\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 if (!selectedFile || !companyId) return;\n try {\n await downloadFile({ companyId, relativePath: selectedFile });\n } catch {\n // Bridge error propagated\n }\n }, [selectedFile, companyId, downloadFile]);\n\n const handleDownloadZip = useCallback(async () => {\n if (!currentPath || !companyId) return;\n try {\n await downloadZip({ companyId, relativePath: currentPath });\n } catch {\n // Bridge error propagated\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 <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 {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 const { companyId } = useHostContext();\n return (\n <div style={{ display: \"grid\", gap: \"0.5rem\" }}>\n <strong>File Browser</strong>\n <FileBrowser companyId={companyId} />\n </div>\n );\n}\n\nexport function FileBrowserTab(props: PluginDetailTabProps) {\n const { companyId } = props.context;\n return <FileBrowser companyId={companyId} />;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useCallback, useState } from \"react\";\nimport {\n usePluginData,\n usePluginAction,\n useHostContext,\n useHostNavigation,\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 const [currentPath, setCurrentPath] = useState(\"\");\n const [selectedFile, setSelectedFile] = useState<string | null>(null);\n\n const { data, loading, error, refresh } = usePluginData<FileTreeData>(\n \"file-tree\",\n { companyId: companyId ?? \"\", relativePath: currentPath }\n );\n\n const downloadFile = usePluginAction(\"download-file\");\n const downloadZip = usePluginAction(\"download-zip\");\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 if (!selectedFile || !companyId) return;\n try {\n await downloadFile({ companyId, relativePath: selectedFile });\n } catch {\n // Bridge error propagated\n }\n }, [selectedFile, companyId, downloadFile]);\n\n const handleDownloadZip = useCallback(async () => {\n if (!currentPath || !companyId) return;\n try {\n await downloadZip({ companyId, relativePath: currentPath });\n } catch {\n // Bridge error propagated\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 <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 {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 const { companyId } = useHostContext();\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 const { companyId } = props.context;\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
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jk3labs/paperclip-plugin-file-browser",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "A file browser plugin for Paperclip workspaces",
6
6
  "files": [