@parallelworks/ui 0.52.0 → 0.53.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/README.md +42 -3
- package/dist/ai/ai.css +5 -3
- package/dist/ai/components/agent/AgentMessageParts.js +1 -1
- package/dist/ai/strings.js +0 -1
- package/dist/ai/ui/sidebar.js +1 -1
- package/dist/components/Field.d.ts +5 -4
- package/dist/components/Field.js +27 -4
- package/dist/components/InfoTip.d.ts +12 -4
- package/dist/components/InfoTip.js +13 -3
- package/dist/components/MarkdownImpl.d.ts +1 -0
- package/dist/components/MarkdownImpl.js +1 -0
- package/dist/components/Popover.d.ts +4 -1
- package/dist/components/Popover.js +2 -1
- package/dist/components/Provider.d.ts +2 -0
- package/dist/components/Provider.js +1 -0
- package/dist/components/focus.d.ts +5 -0
- package/dist/components/focus.js +21 -1
- package/dist/editor/Editor.js +13 -3
- package/dist/editor/Monaco.d.ts +8 -1
- package/dist/editor/Monaco.js +16 -3
- package/dist/editor/editorModel.d.ts +1 -1
- package/dist/editor/editorModel.js +1 -1
- package/dist/editor/index.d.ts +1 -0
- package/dist/editor/index.js +2 -1
- package/dist/editor/languages.d.ts +24 -0
- package/dist/editor/languages.js +31 -0
- package/dist/editor/monacoFeatures.d.ts +7 -0
- package/dist/editor/monacoFeatures.js +19 -0
- package/dist/editor/themes.js +7 -2
- package/dist/editor/workers.d.ts +3 -3
- package/dist/editor/workers.js +8 -4
- package/dist/editor/yamlLanguage.js +55 -38
- package/dist/file-explorer/FileExplorer.d.ts +18 -4
- package/dist/file-explorer/FileExplorer.js +62 -19
- package/dist/file-explorer/FilePreview/Viewer.js +12 -8
- package/dist/file-explorer/FileRow.d.ts +6 -2
- package/dist/file-explorer/FileRow.js +35 -17
- package/dist/file-explorer/FileRowsWindow.d.ts +2 -1
- package/dist/file-explorer/FileRowsWindow.js +2 -1
- package/dist/file-explorer/FileTable.d.ts +3 -1
- package/dist/file-explorer/FileTable.js +3 -2
- package/dist/file-explorer/TreeView.d.ts +5 -1
- package/dist/file-explorer/TreeView.js +49 -25
- package/dist/file-explorer/helpers.js +3 -3
- package/dist/file-explorer/lib/utils.d.ts +3 -0
- package/dist/file-explorer/lib/utils.js +6 -1
- package/dist/form/Wizard/utils.js +2 -1
- package/dist/list/ActionToolbar.js +3 -2
- package/dist/list/ListPage.js +1 -1
- package/dist/list/ListTable.d.ts +5 -0
- package/dist/list/ListTable.js +6 -2
- package/dist/styles.css +1 -1
- package/package.json +5 -2
- package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +0 -6
- package/dist/file-explorer/FilePreview/CodePreviewEditor.js +0 -64
package/dist/editor/themes.js
CHANGED
|
@@ -54,8 +54,13 @@ function editorTheme(style, mode) {
|
|
|
54
54
|
"editor.lineHighlightBackground": c("--theme-hover"),
|
|
55
55
|
"editorLineNumber.foreground": c("--theme-placeholder"),
|
|
56
56
|
"editorLineNumber.activeForeground": c("--theme-muted-text-color"),
|
|
57
|
-
"
|
|
58
|
-
|
|
57
|
+
...mode === "dark" ? {
|
|
58
|
+
"editor.selectionBackground": `${c("--theme-accent")}66`,
|
|
59
|
+
"editor.inactiveSelectionBackground": `${c("--theme-accent")}40`
|
|
60
|
+
} : {
|
|
61
|
+
"editor.selectionBackground": c("--theme-accent-subtle"),
|
|
62
|
+
"editor.inactiveSelectionBackground": c("--theme-hover")
|
|
63
|
+
}
|
|
59
64
|
}
|
|
60
65
|
};
|
|
61
66
|
}
|
package/dist/editor/workers.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Points window.MonacoEnvironment at the bundled workers
|
|
3
|
-
* host that already configured its own
|
|
4
|
-
* calls this on first load only when nothing is configured.
|
|
2
|
+
* Points window.MonacoEnvironment at the bundled workers and those a host added with
|
|
3
|
+
* `addEditorLanguages`. Idempotent, and a host that already configured its own
|
|
4
|
+
* MonacoEnvironment wins — the editor calls this on first load only when nothing is configured.
|
|
5
5
|
*/
|
|
6
6
|
export declare function setupMonacoWorkers(): void;
|
|
7
7
|
/**
|
package/dist/editor/workers.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
+
import { hostEditorWorker } from "./languages.js";
|
|
1
2
|
//#region src/editor/workers.ts
|
|
2
3
|
var monacoEnvironment = { getWorker(_moduleId, label) {
|
|
4
|
+
const host = hostEditorWorker(label);
|
|
5
|
+
if (host) return host();
|
|
3
6
|
if (label === "json") return new Worker(new URL(
|
|
4
7
|
/* @vite-ignore */
|
|
5
8
|
new URL("../assets/json.worker-BKkHJbNB.js", import.meta.url).href,
|
|
6
9
|
"" + import.meta.url
|
|
7
10
|
), { type: "module" });
|
|
8
|
-
return new Worker(new URL(
|
|
11
|
+
if (label === "editorWorkerService") return new Worker(new URL(
|
|
9
12
|
/* @vite-ignore */
|
|
10
13
|
new URL("../assets/editor.worker-Cc3TfJ_-.js", import.meta.url).href,
|
|
11
14
|
"" + import.meta.url
|
|
12
15
|
), { type: "module" });
|
|
16
|
+
throw new Error(`The editor has no worker for Monaco's "${label}" service: pass one to addEditorLanguages.`);
|
|
13
17
|
} };
|
|
14
18
|
/**
|
|
15
|
-
* Points window.MonacoEnvironment at the bundled workers
|
|
16
|
-
* host that already configured its own
|
|
17
|
-
* calls this on first load only when nothing is configured.
|
|
19
|
+
* Points window.MonacoEnvironment at the bundled workers and those a host added with
|
|
20
|
+
* `addEditorLanguages`. Idempotent, and a host that already configured its own
|
|
21
|
+
* MonacoEnvironment wins — the editor calls this on first load only when nothing is configured.
|
|
18
22
|
*/
|
|
19
23
|
function setupMonacoWorkers() {
|
|
20
24
|
if (window.MonacoEnvironment) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createYamlWorker } from "./workers.js";
|
|
2
2
|
import { onYamlSchemas } from "./yaml.js";
|
|
3
|
-
import * as monaco from "monaco-editor";
|
|
3
|
+
import * as monaco from "monaco-editor/editor/editor.api";
|
|
4
4
|
//#region src/editor/yamlLanguage.ts
|
|
5
5
|
var LANGUAGE_ID = "yaml";
|
|
6
6
|
var MARKER_OWNER = "yaml";
|
|
@@ -186,41 +186,59 @@ function answerServer(method, params) {
|
|
|
186
186
|
return null;
|
|
187
187
|
}
|
|
188
188
|
function startYamlLanguage() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const connection = new WorkerConnection(worker, answerServer);
|
|
189
|
+
let server;
|
|
190
|
+
let schemas = [];
|
|
192
191
|
const open = /* @__PURE__ */ new Map();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
192
|
+
function connect() {
|
|
193
|
+
if (server) return server;
|
|
194
|
+
const worker = createYamlWorker();
|
|
195
|
+
worker.postMessage({});
|
|
196
|
+
const connection = new WorkerConnection(worker, answerServer);
|
|
197
|
+
connection.onNotification("textDocument/publishDiagnostics", (params) => {
|
|
198
|
+
const { uri, diagnostics } = params;
|
|
199
|
+
const model = open.get(uri);
|
|
200
|
+
if (model && !model.isDisposed()) monaco.editor.setModelMarkers(model, MARKER_OWNER, diagnostics.map(toMarker));
|
|
201
|
+
});
|
|
202
|
+
server = {
|
|
203
|
+
connection,
|
|
204
|
+
ready: connection.request("initialize", {
|
|
205
|
+
processId: null,
|
|
206
|
+
rootUri: null,
|
|
207
|
+
capabilities: {
|
|
208
|
+
workspace: { configuration: true },
|
|
209
|
+
textDocument: {
|
|
210
|
+
synchronization: {},
|
|
211
|
+
completion: { completionItem: {
|
|
212
|
+
snippetSupport: true,
|
|
213
|
+
insertReplaceSupport: true,
|
|
214
|
+
documentationFormat: ["markdown", "plaintext"]
|
|
215
|
+
} },
|
|
216
|
+
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
217
|
+
publishDiagnostics: {}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}).then(() => {
|
|
221
|
+
connection.notify("initialized", {});
|
|
222
|
+
connection.notify("workspace/didChangeConfiguration", { settings: null });
|
|
223
|
+
sendSchemas();
|
|
224
|
+
})
|
|
225
|
+
};
|
|
226
|
+
return server;
|
|
227
|
+
}
|
|
228
|
+
function notify(method, params) {
|
|
229
|
+
const { connection, ready } = connect();
|
|
230
|
+
ready.then(() => connection.notify(method, params));
|
|
231
|
+
}
|
|
232
|
+
function sendSchemas() {
|
|
233
|
+
server?.connection.notify("json/schemaAssociations", [schemas]);
|
|
234
|
+
}
|
|
235
|
+
onYamlSchemas((added) => {
|
|
236
|
+
schemas = added;
|
|
237
|
+
server?.ready.then(sendSchemas);
|
|
220
238
|
});
|
|
221
239
|
function close(uri, model) {
|
|
222
240
|
open.delete(uri);
|
|
223
|
-
|
|
241
|
+
notify("textDocument/didClose", { textDocument: { uri } });
|
|
224
242
|
if (!model.isDisposed()) monaco.editor.setModelMarkers(model, MARKER_OWNER, []);
|
|
225
243
|
}
|
|
226
244
|
function track(model) {
|
|
@@ -229,26 +247,24 @@ function startYamlLanguage() {
|
|
|
229
247
|
const isYaml = model.getLanguageId() === LANGUAGE_ID;
|
|
230
248
|
if (isYaml && !open.has(uri)) {
|
|
231
249
|
open.set(uri, model);
|
|
232
|
-
|
|
250
|
+
notify("textDocument/didOpen", { textDocument: {
|
|
233
251
|
uri,
|
|
234
252
|
languageId: LANGUAGE_ID,
|
|
235
253
|
version: model.getVersionId(),
|
|
236
254
|
text: model.getValue()
|
|
237
|
-
};
|
|
238
|
-
ready.then(() => connection.notify("textDocument/didOpen", { textDocument }));
|
|
255
|
+
} });
|
|
239
256
|
} else if (!isYaml && open.has(uri)) close(uri, model);
|
|
240
257
|
};
|
|
241
258
|
model.onDidChangeLanguage(sync);
|
|
242
259
|
model.onDidChangeContent(() => {
|
|
243
260
|
if (!open.has(uri)) return;
|
|
244
|
-
|
|
261
|
+
notify("textDocument/didChange", {
|
|
245
262
|
textDocument: {
|
|
246
263
|
uri,
|
|
247
264
|
version: model.getVersionId()
|
|
248
265
|
},
|
|
249
266
|
contentChanges: [{ text: model.getValue() }]
|
|
250
|
-
};
|
|
251
|
-
ready.then(() => connection.notify("textDocument/didChange", params));
|
|
267
|
+
});
|
|
252
268
|
});
|
|
253
269
|
model.onWillDispose(() => {
|
|
254
270
|
if (open.get(uri) === model) close(uri, model);
|
|
@@ -258,6 +274,7 @@ function startYamlLanguage() {
|
|
|
258
274
|
for (const model of monaco.editor.getModels()) track(model);
|
|
259
275
|
monaco.editor.onDidCreateModel(track);
|
|
260
276
|
async function request(method, model, params, token) {
|
|
277
|
+
const { connection, ready } = connect();
|
|
261
278
|
await ready;
|
|
262
279
|
return connection.request(method, {
|
|
263
280
|
textDocument: { uri: model.uri.toString() },
|
|
@@ -2,7 +2,22 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
import { type RowAction } from '../list/index.js';
|
|
3
3
|
import type { IFileExplorerClient, IFileExplorerProvider } from './lib/fileExplorer.js';
|
|
4
4
|
import type { ExplorerObject, TreeNode, TStorage } from './lib/types.js';
|
|
5
|
-
|
|
5
|
+
/** How the explorer moves between folders and files. */
|
|
6
|
+
type ExplorerNavigation = {
|
|
7
|
+
/** The URL of a path, for a host that keeps the path in its URL. Rows, the
|
|
8
|
+
* tree, the trail and each row menu's "Open folder" are then links through
|
|
9
|
+
* the host's router (`useLink`), so a path opens in a new tab, shows its
|
|
10
|
+
* URL and can be copied, and back and forward move between paths. */
|
|
11
|
+
pathHref: (path: string) => string;
|
|
12
|
+
/** Moves to a path without a link: from the keyboard, a delete, an upload.
|
|
13
|
+
* Absent, the explorer goes to `pathHref(path)` through the provider's
|
|
14
|
+
* `navigation.goTo`. */
|
|
15
|
+
onPathChange?: ((path: string) => void) | undefined;
|
|
16
|
+
} | {
|
|
17
|
+
pathHref?: undefined;
|
|
18
|
+
onPathChange: (path: string) => void;
|
|
19
|
+
};
|
|
20
|
+
type IFileExplorerProps = ExplorerNavigation & {
|
|
6
21
|
storages: TStorage[];
|
|
7
22
|
/** Resolves the storage's injected provider pair; the host owns caching. */
|
|
8
23
|
getProviderAndClient: (storage: TStorage) => Promise<{
|
|
@@ -21,7 +36,6 @@ interface IFileExplorerProps {
|
|
|
21
36
|
}[] | undefined;
|
|
22
37
|
groupsLoading?: boolean;
|
|
23
38
|
selectedPath: string;
|
|
24
|
-
onPathChange: (path: string) => void;
|
|
25
39
|
/** Renders only the tree, for a host that embeds it as a navigator and owns
|
|
26
40
|
* the pane beside it (the notebooks page puts its editor there). The host
|
|
27
41
|
* also owns the width, so the tree fills its container instead of carrying
|
|
@@ -48,6 +62,6 @@ interface IFileExplorerProps {
|
|
|
48
62
|
extraRowActions?: ((node: TreeNode, storage: TStorage | undefined) => RowAction[]) | undefined;
|
|
49
63
|
/** Host content for the selected folder's header, before its badges. */
|
|
50
64
|
headerAccessory?: ((node: TreeNode, storage: TStorage | undefined) => ReactNode) | undefined;
|
|
51
|
-
}
|
|
52
|
-
export default function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy, groups, groupsLoading, selectedPath, onPathChange, getProviderAndClient, treeOnly, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, extraRowActions, headerAccessory, }: IFileExplorerProps): import("react").JSX.Element;
|
|
65
|
+
};
|
|
66
|
+
export default function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy, groups, groupsLoading, selectedPath, onPathChange, pathHref, getProviderAndClient, treeOnly, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, extraRowActions, headerAccessory, }: IFileExplorerProps): import("react").JSX.Element;
|
|
53
67
|
export {};
|
|
@@ -7,7 +7,8 @@ import { Loader } from "../components/Loader.js";
|
|
|
7
7
|
import { toast } from "../components/Toast.js";
|
|
8
8
|
import { keyedByContent } from "../components/keys.js";
|
|
9
9
|
import { Tooltip } from "../components/Tooltip.js";
|
|
10
|
-
import { usePersistedState, useSlots, useStrings } from "../components/Provider.js";
|
|
10
|
+
import { useNavigation, usePersistedState, useSlots, useStrings } from "../components/Provider.js";
|
|
11
|
+
import { AppLink } from "../components/AppLink.js";
|
|
11
12
|
import { useCopy } from "../components/useCopy.js";
|
|
12
13
|
import { Button } from "../components/Button.js";
|
|
13
14
|
import { Callout } from "../components/Callout.js";
|
|
@@ -67,6 +68,13 @@ function UriCard({ value }) {
|
|
|
67
68
|
]
|
|
68
69
|
});
|
|
69
70
|
}
|
|
71
|
+
var breadcrumbClass = "max-w-[16rem] cursor-pointer truncate text-(--theme-muted-text-color) transition-colors hover:text-(--theme-app)";
|
|
72
|
+
/** `href` resolved against the page, as the browser's "Copy link" gives it. */
|
|
73
|
+
function absoluteUrl(href) {
|
|
74
|
+
const url = safeUrl(href);
|
|
75
|
+
if (!url || typeof window === "undefined") return url;
|
|
76
|
+
return new URL(url, window.location.href).href;
|
|
77
|
+
}
|
|
70
78
|
var MAX_UPLOAD_SIZE = 85899345920;
|
|
71
79
|
function isUploadSizeExceeded(size) {
|
|
72
80
|
return size > MAX_UPLOAD_SIZE;
|
|
@@ -110,12 +118,13 @@ function TreeFrame({ treeOnly, sidebarWidth, onWidthChange, children }) {
|
|
|
110
118
|
children: [children, /* @__PURE__ */ jsx("div", { ...splitter.separatorProps })]
|
|
111
119
|
});
|
|
112
120
|
}
|
|
113
|
-
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, extraRowActions, headerAccessory }) {
|
|
121
|
+
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, pathHref, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, extraRowActions, headerAccessory }) {
|
|
114
122
|
const copySubmenu = useCopySubmenu();
|
|
115
123
|
const t = useStrings().fileExplorer;
|
|
116
124
|
const { common } = useStrings();
|
|
117
125
|
const { copy } = useCopy();
|
|
118
126
|
const slots = useSlots();
|
|
127
|
+
const { goTo } = useNavigation();
|
|
119
128
|
const fileExplorerContext = useFileExplorer();
|
|
120
129
|
const [listings, setListings] = useState(() => /* @__PURE__ */ new Map());
|
|
121
130
|
const fetchesInFlightRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -146,7 +155,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
146
155
|
if (treeMap[selectedPath]) return selectedPath;
|
|
147
156
|
return `${selectedPath}/`;
|
|
148
157
|
}, [selectedPath, treeMap]);
|
|
149
|
-
const setSelectedPath = onPathChange;
|
|
158
|
+
const setSelectedPath = (path) => onPathChange ? onPathChange(path) : pathHref && goTo(pathHref(path));
|
|
150
159
|
const [expandedPaths, setExpandedPaths] = useState(() => /* @__PURE__ */ new Set(["", ...initialExpandedPaths ?? []]));
|
|
151
160
|
const onExpandedPathsChangeRef = useRef(onExpandedPathsChange);
|
|
152
161
|
onExpandedPathsChangeRef.current = onExpandedPathsChange;
|
|
@@ -161,10 +170,12 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
161
170
|
if (selectedNode?.type === "directory" && selectedNode.storageId) fetchNodeChildren(selectedNode, { replace: true });
|
|
162
171
|
}, [refreshKey, selectedNode]);
|
|
163
172
|
const [checkedItems, setCheckedItems] = useState(/* @__PURE__ */ new Set());
|
|
173
|
+
const [detailTab, setDetailTab] = useState("details");
|
|
164
174
|
const [checkedItemsPath, setCheckedItemsPath] = useState(selectedPath);
|
|
165
175
|
if (checkedItemsPath !== selectedPath) {
|
|
166
176
|
setCheckedItemsPath(selectedPath);
|
|
167
177
|
setCheckedItems(/* @__PURE__ */ new Set());
|
|
178
|
+
setDetailTab("details");
|
|
168
179
|
}
|
|
169
180
|
const selectedNodeChildren = useMemo(() => selectedNode ? getNodeChildren(parentToChildrenMap, selectedNode.path) : [], [parentToChildrenMap, selectedNode]);
|
|
170
181
|
const findStorage = (storageId) => storages.find((s) => s.id === storageId);
|
|
@@ -244,7 +255,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
244
255
|
const folderInputRef = useRef(null);
|
|
245
256
|
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
|
246
257
|
const [pendingDelete, setPendingDelete] = useState(null);
|
|
247
|
-
const [detailTab, setDetailTab] = useState("details");
|
|
248
258
|
useEffect(() => clearPreviewCache, []);
|
|
249
259
|
const { openMenu, contextMenu } = useContextMenu();
|
|
250
260
|
const [sidebarWidth, setSidebarWidth] = usePersistedState(SIDEBAR_WIDTH_STORAGE_KEY, 300);
|
|
@@ -568,6 +578,9 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
568
578
|
const closePreview = () => {
|
|
569
579
|
setPreviewOpen(false);
|
|
570
580
|
};
|
|
581
|
+
const openFromTree = (node) => {
|
|
582
|
+
if (treeOnly && node.type === "file") openPreview(node);
|
|
583
|
+
};
|
|
571
584
|
const openUserNode = (node) => {
|
|
572
585
|
handleSelectTreeItem(node.path, node);
|
|
573
586
|
if (!expandedPaths.has(node.path)) handleToggleExpand(node.path);
|
|
@@ -651,9 +664,16 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
651
664
|
destructive: true,
|
|
652
665
|
onClick: () => deleteNode(node)
|
|
653
666
|
});
|
|
654
|
-
const
|
|
667
|
+
const open = {
|
|
655
668
|
label: isFile ? t.preview.viewDetails : t.preview.openFolder,
|
|
656
|
-
icon: isFile ? /* @__PURE__ */ jsx(FileTextIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {})
|
|
669
|
+
icon: isFile ? /* @__PURE__ */ jsx(FileTextIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {})
|
|
670
|
+
};
|
|
671
|
+
const href = pathHref?.(node.path);
|
|
672
|
+
const lead = [href ? {
|
|
673
|
+
...open,
|
|
674
|
+
href
|
|
675
|
+
} : {
|
|
676
|
+
...open,
|
|
657
677
|
onClick: () => handleSelectTreeItem(node.path, node)
|
|
658
678
|
}];
|
|
659
679
|
const uriExtras = uris.map((value) => ({
|
|
@@ -661,6 +681,12 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
661
681
|
value,
|
|
662
682
|
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
663
683
|
}));
|
|
684
|
+
const pageUrl = absoluteUrl(href);
|
|
685
|
+
if (pageUrl) uriExtras.unshift({
|
|
686
|
+
label: t.preview.link,
|
|
687
|
+
value: pageUrl,
|
|
688
|
+
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
689
|
+
});
|
|
664
690
|
return assembleRowMenu({
|
|
665
691
|
lead,
|
|
666
692
|
actions,
|
|
@@ -705,10 +731,14 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
705
731
|
"aria-current": "location",
|
|
706
732
|
className: "truncate font-semibold text-(--theme-app)",
|
|
707
733
|
children: label
|
|
734
|
+
}) : pathHref ? /* @__PURE__ */ jsx(AppLink, {
|
|
735
|
+
href: pathHref(node.path),
|
|
736
|
+
className: breadcrumbClass,
|
|
737
|
+
children: label
|
|
708
738
|
}) : /* @__PURE__ */ jsx("button", {
|
|
709
739
|
type: "button",
|
|
710
740
|
onClick: () => handleSelectTreeItem(node.path, node),
|
|
711
|
-
className:
|
|
741
|
+
className: breadcrumbClass,
|
|
712
742
|
children: label
|
|
713
743
|
})]
|
|
714
744
|
}, node.path);
|
|
@@ -965,8 +995,10 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
965
995
|
onToggleExpand: handleToggleExpand,
|
|
966
996
|
onSelect: (path, node) => {
|
|
967
997
|
handleSelectTreeItem(path, node);
|
|
968
|
-
|
|
998
|
+
openFromTree(node);
|
|
969
999
|
},
|
|
1000
|
+
pathHref,
|
|
1001
|
+
onFollowLink: openFromTree,
|
|
970
1002
|
onDropToFolder: onDropFiles,
|
|
971
1003
|
onContextMenu: openNodeContextMenu,
|
|
972
1004
|
onPreviewFile: openPreview,
|
|
@@ -1008,18 +1040,28 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
1008
1040
|
children: t.chrome.storages
|
|
1009
1041
|
}),
|
|
1010
1042
|
/* @__PURE__ */ jsx(ListActionsHeader, { actionCount: 1 }),
|
|
1011
|
-
rootNodes.map((rootNode) =>
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
storageCount: getNodeChildren(parentToChildrenMap, rootNode.path).length,
|
|
1015
|
-
getItems: () => [{
|
|
1043
|
+
rootNodes.map((rootNode) => {
|
|
1044
|
+
const href = pathHref?.(rootNode.path);
|
|
1045
|
+
const open = {
|
|
1016
1046
|
label: t.preview.open,
|
|
1017
|
-
icon: /* @__PURE__ */ jsx(FolderIcon, {})
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1047
|
+
icon: /* @__PURE__ */ jsx(FolderIcon, {})
|
|
1048
|
+
};
|
|
1049
|
+
return /* @__PURE__ */ jsx(UserRow, {
|
|
1050
|
+
node: rootNode,
|
|
1051
|
+
icon: getNodeIcon(rootNode),
|
|
1052
|
+
href,
|
|
1053
|
+
storageCount: getNodeChildren(parentToChildrenMap, rootNode.path).length,
|
|
1054
|
+
getItems: () => [href ? {
|
|
1055
|
+
...open,
|
|
1056
|
+
href
|
|
1057
|
+
} : {
|
|
1058
|
+
...open,
|
|
1059
|
+
onClick: () => openUserNode(rootNode)
|
|
1060
|
+
}],
|
|
1061
|
+
openMenu,
|
|
1062
|
+
onOpen: openUserNode
|
|
1063
|
+
}, rootNode.path);
|
|
1064
|
+
})
|
|
1023
1065
|
]
|
|
1024
1066
|
}), rootNodes.length === 0 && /* @__PURE__ */ jsx(NoDataAvailablePreview, {
|
|
1025
1067
|
selectedStorage: null,
|
|
@@ -1206,6 +1248,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
1206
1248
|
onOpen: (n) => handleSelectTreeItem(n.path, n),
|
|
1207
1249
|
onToggleCheck: handleItemCheckbox,
|
|
1208
1250
|
onPreview: openPreview,
|
|
1251
|
+
pathHref,
|
|
1209
1252
|
loadMore: loadMoreProps
|
|
1210
1253
|
}, selectedNode.path) : /* @__PURE__ */ jsxs("div", {
|
|
1211
1254
|
className: "h-full flex flex-col",
|
|
@@ -6,6 +6,7 @@ import { useStrings } from "../../components/Provider.js";
|
|
|
6
6
|
import { Button } from "../../components/Button.js";
|
|
7
7
|
import Markdown from "../../components/Markdown.js";
|
|
8
8
|
import { formatBytes } from "../../format.js";
|
|
9
|
+
import { Editor } from "../../editor/Editor.js";
|
|
9
10
|
import { getFileExtension } from "../lib/utils.js";
|
|
10
11
|
import { getMonacoLanguage, previewLimitsFor } from "./previewType.js";
|
|
11
12
|
import { CorsIssuePreview } from "../helpers.js";
|
|
@@ -14,9 +15,8 @@ import { countLines } from "./readCappedBytes.js";
|
|
|
14
15
|
import { useObjectBlobUrl } from "./useObjectBlobUrl.js";
|
|
15
16
|
import { useObjectText } from "./useObjectText.js";
|
|
16
17
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
-
import {
|
|
18
|
+
import { useCallback, useMemo, useState } from "react";
|
|
18
19
|
//#region src/file-explorer/FilePreview/Viewer.tsx
|
|
19
|
-
var CodePreviewEditor = lazy(() => import("./CodePreviewEditor.js"));
|
|
20
20
|
var PREVIEW_MARKDOWN_CONTROLS = {
|
|
21
21
|
table: { fullscreen: false },
|
|
22
22
|
mermaid: { fullscreen: false }
|
|
@@ -279,12 +279,16 @@ function CodeView({ node, url, onDownload, storage, onCorsRetry }) {
|
|
|
279
279
|
controls: PREVIEW_MARKDOWN_CONTROLS,
|
|
280
280
|
children: text ?? ""
|
|
281
281
|
})
|
|
282
|
-
}) : /* @__PURE__ */ jsx(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
282
|
+
}) : /* @__PURE__ */ jsx(Editor, {
|
|
283
|
+
readOnly: true,
|
|
284
|
+
value: text ?? "",
|
|
285
|
+
language,
|
|
286
|
+
ariaLabel: node.name,
|
|
287
|
+
className: "!rounded-none !border-0",
|
|
288
|
+
fontSize: 12,
|
|
289
|
+
minimap: { enabled: false },
|
|
290
|
+
overviewRulerLanes: 0,
|
|
291
|
+
wordWrap: "off"
|
|
288
292
|
})
|
|
289
293
|
})
|
|
290
294
|
})]
|
|
@@ -16,6 +16,8 @@ interface FileRowProps {
|
|
|
16
16
|
rowIndex: number;
|
|
17
17
|
/** The keyboard cursor row. Focus stays on the container, so nothing else marks it. */
|
|
18
18
|
active: boolean;
|
|
19
|
+
/** Makes the row a link to the node. */
|
|
20
|
+
href?: string | undefined;
|
|
19
21
|
checked: boolean;
|
|
20
22
|
canPreview: boolean;
|
|
21
23
|
getItems: () => MenuEntry[];
|
|
@@ -24,13 +26,15 @@ interface FileRowProps {
|
|
|
24
26
|
onToggleCheck: (node: TreeNode, checked: boolean) => void;
|
|
25
27
|
onPreview: (node: TreeNode) => void;
|
|
26
28
|
}
|
|
27
|
-
export declare function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen, }: {
|
|
29
|
+
export declare function UserRow({ node, icon, href, storageCount, getItems, openMenu, onOpen, }: {
|
|
28
30
|
node: TreeNode;
|
|
29
31
|
icon: ReactNode;
|
|
32
|
+
/** Makes the row a link to the user. */
|
|
33
|
+
href?: string | undefined;
|
|
30
34
|
storageCount: number;
|
|
31
35
|
getItems: () => MenuEntry[];
|
|
32
36
|
openMenu: OpenMenu;
|
|
33
37
|
onOpen: (node: TreeNode) => void;
|
|
34
38
|
}): import("react").JSX.Element;
|
|
35
|
-
export declare function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview, }: FileRowProps): import("react").JSX.Element;
|
|
39
|
+
export declare function FileRow({ node, icon, rowId, rowIndex, active, href, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview, }: FileRowProps): import("react").JSX.Element;
|
|
36
40
|
export {};
|
|
@@ -2,10 +2,11 @@ import { cx } from "../cx.js";
|
|
|
2
2
|
import { PreviewIcon } from "../icons.js";
|
|
3
3
|
import { Tooltip } from "../components/Tooltip.js";
|
|
4
4
|
import { useStrings } from "../components/Provider.js";
|
|
5
|
+
import { AppLink, isPlainClick } from "../components/AppLink.js";
|
|
5
6
|
import { formatBytes } from "../format.js";
|
|
6
7
|
import { RelativeTime } from "../components/relativeTime.js";
|
|
7
8
|
import { Table } from "../components/Table.js";
|
|
8
|
-
import { RowSelectCheckbox } from "../list/ListTable.js";
|
|
9
|
+
import { RowSelectCheckbox, STRETCHED_LINK } from "../list/ListTable.js";
|
|
9
10
|
import { MoreButton, rowContextMenuProps, useRowMenuActive } from "../list/rowMenu.js";
|
|
10
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
12
|
import { DateTime } from "luxon";
|
|
@@ -20,16 +21,31 @@ function fileRowHeight(node) {
|
|
|
20
21
|
function isInteractiveTarget(e) {
|
|
21
22
|
return !!e.target.closest("a,button,input,label");
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
/** Opens the row on a click its controls don't take. Beside a link, which
|
|
25
|
+
* covers the row, that is only a click on a gap the link leaves, and a
|
|
26
|
+
* modified one there asks for a new tab, which only the link can give. */
|
|
27
|
+
function rowClickHandler(onOpen, href) {
|
|
28
|
+
return (e) => {
|
|
29
|
+
if (isInteractiveTarget(e) || href && !isPlainClick(e)) return;
|
|
30
|
+
onOpen();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** A row's name: with an href, the link that covers the row, so the whole row
|
|
34
|
+
* opens in a new tab, shows its URL and reads as a link. */
|
|
35
|
+
function RowName({ name, href }) {
|
|
36
|
+
return href ? /* @__PURE__ */ jsx(AppLink, {
|
|
37
|
+
href,
|
|
38
|
+
"data-row-link": true,
|
|
39
|
+
className: STRETCHED_LINK,
|
|
40
|
+
children: name
|
|
41
|
+
}) : name;
|
|
42
|
+
}
|
|
43
|
+
function UserRow({ node, icon, href, storageCount, getItems, openMenu, onOpen }) {
|
|
24
44
|
const t = useStrings().fileExplorer;
|
|
25
45
|
const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
|
|
26
|
-
const handleRowClick = (e) => {
|
|
27
|
-
if (isInteractiveTarget(e)) return;
|
|
28
|
-
onOpen(node);
|
|
29
|
-
};
|
|
30
46
|
return /* @__PURE__ */ jsxs("tr", {
|
|
31
|
-
className: cx("group cursor-pointer", menuActive ? "theme-hover" : "hover:theme-hover"),
|
|
32
|
-
onClick:
|
|
47
|
+
className: cx("group cursor-pointer", menuActive ? "theme-hover" : "hover:theme-hover", href && "relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:z-[1]"),
|
|
48
|
+
onClick: rowClickHandler(() => onOpen(node), href),
|
|
33
49
|
...rowContextMenuProps(openRowMenu, getItems),
|
|
34
50
|
children: [
|
|
35
51
|
/* @__PURE__ */ jsx(Table.Item, {
|
|
@@ -41,7 +57,10 @@ function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen }) {
|
|
|
41
57
|
children: icon
|
|
42
58
|
}), /* @__PURE__ */ jsx("span", {
|
|
43
59
|
className: "truncate text-[13px] font-medium text-(--theme-app)",
|
|
44
|
-
children:
|
|
60
|
+
children: /* @__PURE__ */ jsx(RowName, {
|
|
61
|
+
name: node.displayName || node.name,
|
|
62
|
+
href
|
|
63
|
+
})
|
|
45
64
|
})]
|
|
46
65
|
})
|
|
47
66
|
}),
|
|
@@ -66,22 +85,18 @@ function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen }) {
|
|
|
66
85
|
]
|
|
67
86
|
});
|
|
68
87
|
}
|
|
69
|
-
function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
|
|
88
|
+
function FileRow({ node, icon, rowId, rowIndex, active, href, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
|
|
70
89
|
const t = useStrings().fileExplorer;
|
|
71
90
|
const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
|
|
72
91
|
const isDir = node.type === "directory";
|
|
73
92
|
const modifiedDt = node.modified ? DateTime.fromISO(node.modified) : null;
|
|
74
93
|
const typeLabel = node.root ? node.storageType ?? node.type : node.type;
|
|
75
|
-
const handleRowClick = (e) => {
|
|
76
|
-
if (isInteractiveTarget(e)) return;
|
|
77
|
-
onOpen(node);
|
|
78
|
-
};
|
|
79
94
|
return /* @__PURE__ */ jsxs("tr", {
|
|
80
95
|
id: rowId,
|
|
81
96
|
"aria-rowindex": rowIndex,
|
|
82
97
|
"aria-selected": checked,
|
|
83
|
-
className: cx("group", checked ? "bg-(--theme-accent-subtle)/60" : menuActive ? "theme-hover" : "hover:theme-hover", active && "group-focus-visible/grid:ring-1 group-focus-visible/grid:ring-inset group-focus-visible/grid:ring-(--theme-link)", "cursor-pointer"),
|
|
84
|
-
onClick:
|
|
98
|
+
className: cx("group", checked ? "bg-(--theme-accent-subtle)/60" : menuActive ? "theme-hover" : "hover:theme-hover", active && "group-focus-visible/grid:ring-1 group-focus-visible/grid:ring-inset group-focus-visible/grid:ring-(--theme-link)", "cursor-pointer", href && "relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:z-[1]"),
|
|
99
|
+
onClick: rowClickHandler(() => onOpen(node), href),
|
|
85
100
|
style: { height: fileRowHeight(node) },
|
|
86
101
|
...rowContextMenuProps(openRowMenu, getItems),
|
|
87
102
|
children: [
|
|
@@ -103,7 +118,10 @@ function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, get
|
|
|
103
118
|
className: "min-w-0",
|
|
104
119
|
children: [/* @__PURE__ */ jsx("div", {
|
|
105
120
|
className: "truncate text-[13px] font-medium leading-tight text-(--theme-app)",
|
|
106
|
-
children:
|
|
121
|
+
children: /* @__PURE__ */ jsx(RowName, {
|
|
122
|
+
name: node.displayName || node.name,
|
|
123
|
+
href
|
|
124
|
+
})
|
|
107
125
|
}), isTwoLineRow(node) && /* @__PURE__ */ jsx("div", {
|
|
108
126
|
className: "truncate text-xs leading-tight text-(--theme-muted-text-color)",
|
|
109
127
|
children: node.contentType
|
|
@@ -30,8 +30,9 @@ interface FileRowsWindowProps {
|
|
|
30
30
|
onOpen: (node: TreeNode) => void;
|
|
31
31
|
onToggleCheck: (node: TreeNode, checked: boolean) => void;
|
|
32
32
|
onPreview: (node: TreeNode) => void;
|
|
33
|
+
pathHref?: ((path: string) => string) | undefined;
|
|
33
34
|
}
|
|
34
35
|
/** Must stay a single child of `<Table>`: Table re-keys children by traversal
|
|
35
36
|
* index, so a count that varied with scroll position would remount every row. */
|
|
36
|
-
export declare function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview, }: FileRowsWindowProps): import("react").JSX.Element;
|
|
37
|
+
export declare function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref, }: FileRowsWindowProps): import("react").JSX.Element;
|
|
37
38
|
export {};
|
|
@@ -23,7 +23,7 @@ function spacerHeights(items, totalSize, scrollMargin) {
|
|
|
23
23
|
}
|
|
24
24
|
/** Must stay a single child of `<Table>`: Table re-keys children by traversal
|
|
25
25
|
* index, so a count that varied with scroll position would remount every row. */
|
|
26
|
-
function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
|
|
26
|
+
function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref }) {
|
|
27
27
|
useNestedScrollerWarning(scrollEl, nodes.length);
|
|
28
28
|
useRowPitchAudit(scrollEl, nodes, items);
|
|
29
29
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -44,6 +44,7 @@ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeI
|
|
|
44
44
|
rowId: `${rowIdPrefix}-${item.index}`,
|
|
45
45
|
rowIndex: item.index + 2,
|
|
46
46
|
active: item.index === activeIndex,
|
|
47
|
+
href: pathHref?.(node.path),
|
|
47
48
|
node,
|
|
48
49
|
icon,
|
|
49
50
|
checked: checkedItems.has(node.path),
|
|
@@ -13,11 +13,13 @@ interface FileTableProps {
|
|
|
13
13
|
onOpen: (node: TreeNode) => void;
|
|
14
14
|
onToggleCheck: (node: TreeNode, checked: boolean) => void;
|
|
15
15
|
onPreview: (node: TreeNode) => void;
|
|
16
|
+
/** The URL of a path; set, each row is a link to its node. */
|
|
17
|
+
pathHref?: ((path: string) => string) | undefined;
|
|
16
18
|
/** Set when the folder has pages left to fetch. */
|
|
17
19
|
loadMore?: LoadMoreState | undefined;
|
|
18
20
|
}
|
|
19
21
|
/** The file table. Give it a `key` of the folder path: a folder change must reset
|
|
20
22
|
* the cursor, the scroll offset and the virtualizer's measurements, and a remount
|
|
21
23
|
* does all three without a synchronising effect. */
|
|
22
|
-
export declare function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll, getItems, openMenu, onOpen, onToggleCheck, onPreview, loadMore, }: FileTableProps): import("react").JSX.Element;
|
|
24
|
+
export declare function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref, loadMore, }: FileTableProps): import("react").JSX.Element;
|
|
23
25
|
export {};
|