@parallelworks/ui 0.18.0 → 0.19.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 +86 -6
- package/dist/ai/adapter/memoryStore.d.ts +8 -0
- package/dist/ai/adapter/mock/index.d.ts +2 -0
- package/dist/ai/adapter/mock/index.js +81 -0
- package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
- package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
- package/dist/ai/adapter/openai/errors.d.ts +3 -0
- package/dist/ai/adapter/openai/index.d.ts +6 -0
- package/dist/ai/adapter/openai/index.js +334 -0
- package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
- package/dist/ai/adapter/openai/responses.d.ts +4 -0
- package/dist/ai/adapter/openai/sse.d.ts +24 -0
- package/dist/ai/adapter/openai/wire.d.ts +40 -0
- package/dist/ai/adapter/types.d.ts +131 -0
- package/dist/ai/agent/classifier.d.ts +2 -0
- package/dist/ai/agent/diff.d.ts +35 -0
- package/dist/ai/agent/diffHighlight.d.ts +6 -0
- package/dist/ai/agent/index.d.ts +6 -0
- package/dist/ai/agent/permissions.d.ts +11 -0
- package/dist/ai/agent/toolfmt.d.ts +18 -0
- package/dist/ai/agent/transcript.d.ts +38 -0
- package/dist/ai/ai.css +325 -0
- package/dist/ai/components/AllocationSelector.d.ts +4 -0
- package/dist/ai/components/AttachmentManager.d.ts +1 -0
- package/dist/ai/components/AttachmentUpload.d.ts +24 -0
- package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
- package/dist/ai/components/BranchNavigator.d.ts +8 -0
- package/dist/ai/components/ChatEmptyState.d.ts +3 -0
- package/dist/ai/components/ChatInput.d.ts +45 -0
- package/dist/ai/components/ChatLayout.d.ts +5 -0
- package/dist/ai/components/ChatMessage.d.ts +24 -0
- package/dist/ai/components/ChatMessageList.d.ts +35 -0
- package/dist/ai/components/ChatSidebar.d.ts +5 -0
- package/dist/ai/components/ChatThread.d.ts +3 -0
- package/dist/ai/components/ComposerChrome.d.ts +18 -0
- package/dist/ai/components/ComposerFrame.d.ts +16 -0
- package/dist/ai/components/DragOverlay.d.ts +5 -0
- package/dist/ai/components/ModelSelector.d.ts +6 -0
- package/dist/ai/components/ShareDialog.d.ts +11 -0
- package/dist/ai/components/SlashMenu.d.ts +28 -0
- package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
- package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
- package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
- package/dist/ai/components/attachmentKeys.d.ts +4 -0
- package/dist/ai/core/ChatProvider.d.ts +66 -0
- package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
- package/dist/ai/core/chatReducer.d.ts +114 -0
- package/dist/ai/core/config.d.ts +47 -0
- package/dist/ai/core/events.d.ts +1 -0
- package/dist/ai/core/exportConversation.d.ts +4 -0
- package/dist/ai/core/greeting.d.ts +3 -0
- package/dist/ai/core/parts.d.ts +11 -0
- package/dist/ai/core/useDragDrop.d.ts +14 -0
- package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
- package/dist/ai/core/useSSEStream.d.ts +34 -0
- package/dist/ai/index.d.ts +40 -0
- package/dist/ai/index.js +6645 -0
- package/dist/ai/strings.d.ts +167 -0
- package/dist/ai/types.d.ts +236 -0
- package/dist/ai/ui/Avatar.d.ts +8 -0
- package/dist/ai/ui/Dropdown.d.ts +31 -0
- package/dist/ai/ui/Markdown.d.ts +8 -0
- package/dist/ai/ui/Notice.d.ts +21 -0
- package/dist/ai/ui/buttons.d.ts +1 -0
- package/dist/ai/ui/codeBreaks.d.ts +16 -0
- package/dist/ai/ui/prose.d.ts +1 -0
- package/dist/ai/ui/sidebar.d.ts +81 -0
- package/dist/ai/utils.d.ts +4 -0
- package/dist/assets/{editor.worker-CuBRIwi-.js → editor.worker-BmHUIVG4.js} +194 -163
- package/dist/assets/{json.worker-8ytS02vz.js → json.worker-D8bJ92nw.js} +384 -338
- package/dist/assets/{yaml.worker-DJC-VUcv.js → yaml.worker-C4gyGAko.js} +15657 -13823
- package/dist/chunks/{CodePreviewEditor-4lx3kE-w.js → CodePreviewEditor-YqQBz1YL.js} +1 -1
- package/dist/chunks/ConfirmModal-aOR4O1QE.js +79 -0
- package/dist/chunks/{FieldWrapper-Bef01EWO.js → FieldWrapper-Cszwg-4B.js} +3 -7
- package/dist/chunks/{CreateModal-BY6DLmur.js → Markdown-CM-FenNE.js} +19 -91
- package/dist/chunks/Monaco-DJ8MW-Dq.js +110 -0
- package/dist/chunks/{PageHeader-l49p1nM9.js → PageHeader-0kG_iypP.js} +67 -65
- package/dist/chunks/{Provider-Ghua9PkN.js → Provider-CDYurraE.js} +54 -20
- package/dist/chunks/{SwitchToggle-CHPP8uZQ.js → SwitchToggle-DShNdcao.js} +135 -132
- package/dist/chunks/{Table-4OHjdKdO.js → Table-DU2MW8xz.js} +5 -3
- package/dist/chunks/{Tooltip-Crxo_eU_.js → Tooltip-K5HJr4_p.js} +31 -4
- package/dist/chunks/duration-BOv8Phl8.js +17 -0
- package/dist/chunks/{editor-9_uVtUyy.js → editor-Dk1rbV75.js} +32 -18
- package/dist/chunks/focus-D_iZJTW1.js +7 -0
- package/dist/chunks/{list-qrojZZNv.js → list-CJm875m7.js} +377 -370
- package/dist/chunks/logviewer-BTKuQWzw.js +828 -0
- package/dist/chunks/memoryStore-CC91S5LP.js +152 -0
- package/dist/chunks/types-Yy0NrerF.js +11 -0
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Breadcrumbs.d.ts +1 -1
- package/dist/components/Dropdown.d.ts +1 -1
- package/dist/components/EmptyState.d.ts +1 -1
- package/dist/components/Indicator.d.ts +2 -2
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/LetterBadge.d.ts +1 -1
- package/dist/components/MarkdownImpl.d.ts +1 -1
- package/dist/components/Provider.d.ts +16 -65
- package/dist/components/SettingRow.d.ts +1 -1
- package/dist/components/SettingsGroup.d.ts +1 -1
- package/dist/components/focus.d.ts +2 -0
- package/dist/components/keys.d.ts +12 -0
- package/dist/duration.d.ts +3 -0
- package/dist/editor/Monaco.d.ts +1 -20
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/index.js +2 -2
- package/dist/editor/yaml.d.ts +5 -0
- package/dist/engine.d.ts +98 -0
- package/dist/file-explorer/FileExplorer.d.ts +11 -2
- package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
- package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
- package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
- package/dist/file-explorer/FileRow.d.ts +1 -1
- package/dist/file-explorer/helpers.d.ts +4 -4
- package/dist/file-explorer/index.d.ts +3 -3
- package/dist/file-explorer/index.js +613 -647
- package/dist/file-explorer/lib/types.d.ts +6 -1
- package/dist/file-explorer/lib/utils.d.ts +1 -3
- package/dist/file-explorer/useNearEnd.d.ts +1 -1
- package/dist/form/FieldWrapper.d.ts +2 -2
- package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
- package/dist/form/Form.d.ts +4 -21
- package/dist/form/index.d.ts +11 -12
- package/dist/form/index.js +1488 -1611
- package/dist/form/lib.d.ts +6 -1
- package/dist/form/types/fieldComponentTypes.d.ts +3 -2
- package/dist/form/types/fieldTypes.d.ts +6 -20
- package/dist/form/useParsedOpts.d.ts +1 -1
- package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
- package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
- package/dist/{workflow → graph}/index.d.ts +1 -1
- package/dist/{workflow → graph}/index.js +215 -180
- package/dist/{workflow → graph}/types.d.ts +3 -3
- package/dist/icons.d.ts +20 -21
- package/dist/index.d.ts +38 -40
- package/dist/index.js +347 -346
- package/dist/list/ListTable.d.ts +1 -1
- package/dist/list/ListViewControls.d.ts +2 -2
- package/dist/list/index.d.ts +9 -9
- package/dist/list/index.js +2 -2
- package/dist/logviewer/LogViewer.d.ts +1 -1
- package/dist/logviewer/index.d.ts +1 -0
- package/dist/logviewer/index.js +2 -742
- package/dist/logviewer/segments.d.ts +9 -0
- package/dist/styles/fonts.css +4 -4
- package/dist/styles/theme.css +6 -4
- package/dist/styles.css +1 -1
- package/dist/theme/index.js +2 -2
- package/package.json +38 -14
- package/LICENSE +0 -202
- package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
- package/dist/editor/usesGitlab.d.ts +0 -97
- package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
- package/dist/form/types/workflow-schema.d.ts +0 -1145
- package/dist/form/utils/pwResolver.d.ts +0 -18
- package/dist/chunks/{MarkdownImpl-Bq4QhrtA.js → MarkdownImpl-CyeTVvSt.js} +1 -1
- /package/dist/{workflow → graph}/AnnotationBanner.d.ts +0 -0
- /package/dist/{workflow → graph}/Collapse.d.ts +0 -0
- /package/dist/{workflow → graph}/DependencyGraph.d.ts +0 -0
- /package/dist/{workflow → graph}/JobSummary.d.ts +0 -0
- /package/dist/{workflow → graph}/Reveal.d.ts +0 -0
- /package/dist/{workflow → graph}/TreeView.d.ts +0 -0
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { i as useNotify, l as useSlots,
|
|
2
|
-
import { _ as useRowMenuActive, d as assembleRowMenu, g as useRowMenu, h as useCopySubmenu, u as MoreButton } from "../chunks/PageHeader-l49p1nM9.js";
|
|
1
|
+
import { i as useNotify, l as useSlots, s as useProvisionCorsRules, u as useStrings } from "../chunks/Provider-CDYurraE.js";
|
|
3
2
|
import { AccessIcon, AlertIcon, AngleDownIcon, AngleRightIcon, AngleUpIcon, ChevronLeftIcon, ChevronRightIcon, ClipboardIcon, CloseIcon, CloudUploadIcon, CollapseIcon, CustomizeIcon, DiskIcon, DocumentIcon, DownloadFileIcon, ExpandIcon, ExpandVerticalIcon, EyeIcon, FileIcon, FileTextIcon, FolderIcon, FolderOpenIcon, ImageIcon, LinkIcon, LockIcon, MinimizeIcon, MusicIcon, PdfIcon, RefreshIcon, ShareIcon, SpreadsheetIcon, SuccessIcon, TrashIcon, UploadIcon, UserIcon, VideoIcon, ZipIcon, ZoomInIcon, ZoomOutIcon } from "../icons.js";
|
|
4
|
-
import { c as
|
|
5
|
-
import {
|
|
3
|
+
import { c as withPositionKeys, n as TOOLTIP_ID, o as keyedByContent, r as TooltipInfo, s as positionKeys } from "../chunks/Tooltip-K5HJr4_p.js";
|
|
4
|
+
import { _ as useRowMenuActive, d as assembleRowMenu, g as useRowMenu, h as useCopySubmenu, u as MoreButton } from "../chunks/PageHeader-0kG_iypP.js";
|
|
5
|
+
import { d as Callout, l as CopyCodeBlock, r as CreateModal, t as Markdown, u as CopyToClipboard } from "../chunks/Markdown-CM-FenNE.js";
|
|
6
|
+
import { t as ConfirmModal } from "../chunks/ConfirmModal-aOR4O1QE.js";
|
|
6
7
|
import { t as StatusBadge } from "../chunks/StatusBadge-C1Blx_cW.js";
|
|
7
8
|
import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
|
|
8
|
-
import { n as Table } from "../chunks/Table-
|
|
9
|
-
import {
|
|
9
|
+
import { n as Table } from "../chunks/Table-DU2MW8xz.js";
|
|
10
|
+
import { A as listTableProps, D as RowSelectCheckbox, S as ListActionsHeader, k as listTableFixedProps, w as ListSkeleton } from "../chunks/list-CJm875m7.js";
|
|
11
|
+
import { Menu, MenuButton, MenuItem, MenuItems, MenuSeparator } from "@headlessui/react";
|
|
10
12
|
import cx from "classnames";
|
|
11
13
|
import { Suspense, createContext, lazy, memo, use, useCallback, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
|
|
12
14
|
import { useLocalStorage } from "usehooks-ts";
|
|
13
15
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
14
16
|
import { createPortal } from "react-dom";
|
|
15
|
-
import { Menu, MenuButton, MenuItem, MenuItems, MenuSeparator } from "@headlessui/react";
|
|
16
17
|
import Skeleton from "react-loading-skeleton";
|
|
17
18
|
import { DateTime } from "luxon";
|
|
18
19
|
import { Resizable } from "re-resizable";
|
|
@@ -264,19 +265,6 @@ async function collectObjectKeysToDelete(nodes, listKeysUnderPrefix) {
|
|
|
264
265
|
}
|
|
265
266
|
return Array.from(keys);
|
|
266
267
|
}
|
|
267
|
-
function getPwUri(username, pwName, key) {
|
|
268
|
-
return `pw://${username}/${pwName}/${key}`;
|
|
269
|
-
}
|
|
270
|
-
var cspUriSchemes = {
|
|
271
|
-
aws: "s3",
|
|
272
|
-
google: "gs",
|
|
273
|
-
azure: "az"
|
|
274
|
-
};
|
|
275
|
-
function getCspUri(csp, cspBucketName, key) {
|
|
276
|
-
const scheme = cspUriSchemes[csp];
|
|
277
|
-
if (!scheme) return null;
|
|
278
|
-
return `${scheme}://${cspBucketName}/${key}`;
|
|
279
|
-
}
|
|
280
268
|
function getUriScheme(uri) {
|
|
281
269
|
return uri.match(/^([a-z0-9+.-]+):\/\//i)?.[1] ?? "";
|
|
282
270
|
}
|
|
@@ -398,17 +386,7 @@ function FileProgressBar({ fileProgress, fileName, progressBarColorScheme, onCan
|
|
|
398
386
|
const isFailed = fileProgress.status === "failed";
|
|
399
387
|
const isUploading = fileProgress.status === "uploading" || !isCompleted && !isCancelled && !isFailed;
|
|
400
388
|
const canCancel = isUploading && fileProgress.abortController && !isCompleted && !isCancelled && !isFailed;
|
|
401
|
-
const barColor =
|
|
402
|
-
if (isCompleted) return progressBarColorScheme.completed;
|
|
403
|
-
if (isCancelled) return progressBarColorScheme.cancelled;
|
|
404
|
-
if (isFailed) return progressBarColorScheme.failed;
|
|
405
|
-
return progressBarColorScheme.uploading;
|
|
406
|
-
}, [
|
|
407
|
-
isCompleted,
|
|
408
|
-
isCancelled,
|
|
409
|
-
isFailed,
|
|
410
|
-
isUploading
|
|
411
|
-
]);
|
|
389
|
+
const barColor = isCompleted ? progressBarColorScheme.completed : isCancelled ? progressBarColorScheme.cancelled : isFailed ? progressBarColorScheme.failed : progressBarColorScheme.uploading;
|
|
412
390
|
const handleCancelClick = (e) => {
|
|
413
391
|
e.stopPropagation();
|
|
414
392
|
setShowCancelConfirm(true);
|
|
@@ -533,17 +511,7 @@ function UploadSessionToast({ session, queueLength, onCancel, onCancelFile }) {
|
|
|
533
511
|
failed: "bg-red-600",
|
|
534
512
|
cancelled: "bg-(--theme-muted-text-color)"
|
|
535
513
|
};
|
|
536
|
-
const barColor =
|
|
537
|
-
if (isCompleted) return progressBarColorScheme.completed;
|
|
538
|
-
if (isCancelled) return progressBarColorScheme.cancelled;
|
|
539
|
-
if (isFailed) return progressBarColorScheme.failed;
|
|
540
|
-
return progressBarColorScheme.uploading;
|
|
541
|
-
}, [
|
|
542
|
-
isCompleted,
|
|
543
|
-
isCancelled,
|
|
544
|
-
isFailed,
|
|
545
|
-
isUploading
|
|
546
|
-
]);
|
|
514
|
+
const barColor = isCompleted ? progressBarColorScheme.completed : isCancelled ? progressBarColorScheme.cancelled : isFailed ? progressBarColorScheme.failed : progressBarColorScheme.uploading;
|
|
547
515
|
const allFiles = session.progress?.allFiles ? Array.from(session.progress.allFiles.values()) : [];
|
|
548
516
|
const uploadingFiles = session.progress?.allFiles ? Array.from(session.progress.allFiles.values()).filter((file) => file.percentage && file.percentage >= 0 && file.percentage < 100) : [];
|
|
549
517
|
const shouldShowSessionCancelButton = !showCancelConfirm && (isUploading || isQueued) && session.uploadNodes.length > 1;
|
|
@@ -603,10 +571,10 @@ function UploadSessionToast({ session, queueLength, onCancel, onCancelFile }) {
|
|
|
603
571
|
className: "text-xs theme-muted-text mt-0.5",
|
|
604
572
|
children: [
|
|
605
573
|
session.progress?.completedFiles || 0,
|
|
606
|
-
" /",
|
|
607
|
-
" ",
|
|
574
|
+
" / ",
|
|
608
575
|
session.progress?.totalFiles || 0,
|
|
609
|
-
"
|
|
576
|
+
" ",
|
|
577
|
+
"files done"
|
|
610
578
|
]
|
|
611
579
|
}), /* @__PURE__ */ jsx("div", {
|
|
612
580
|
className: "text-xs theme-muted-text mt-0.5",
|
|
@@ -642,10 +610,10 @@ function UploadSessionToast({ session, queueLength, onCancel, onCancelFile }) {
|
|
|
642
610
|
uploadingFiles.length > 0 && `${uploadingFiles.length} uploading `,
|
|
643
611
|
"(",
|
|
644
612
|
session.progress?.completedFiles || 0,
|
|
645
|
-
" /",
|
|
646
|
-
" ",
|
|
613
|
+
" / ",
|
|
647
614
|
session.progress?.totalFiles || 0,
|
|
648
|
-
"
|
|
615
|
+
" ",
|
|
616
|
+
"done)"
|
|
649
617
|
]
|
|
650
618
|
})] })]
|
|
651
619
|
}), /* @__PURE__ */ jsx("button", {
|
|
@@ -1048,11 +1016,11 @@ function FileExplorerProvider({ children }) {
|
|
|
1048
1016
|
}),
|
|
1049
1017
|
/* @__PURE__ */ jsx("ul", {
|
|
1050
1018
|
className: "text-sm space-y-1 max-h-32 overflow-y-auto",
|
|
1051
|
-
children: failedFileNames.map((name, i) => /* @__PURE__ */ jsxs("li", { children: [
|
|
1019
|
+
children: keyedByContent(failedFileNames, (n) => n).map(({ key, item: name }, i) => /* @__PURE__ */ jsxs("li", { children: [
|
|
1052
1020
|
"• ",
|
|
1053
1021
|
name,
|
|
1054
1022
|
actualFailedResults[i]?.reason ? `: ${actualFailedResults[i].reason}` : ""
|
|
1055
|
-
] },
|
|
1023
|
+
] }, key))
|
|
1056
1024
|
}),
|
|
1057
1025
|
/* @__PURE__ */ jsx("div", {
|
|
1058
1026
|
className: "mt-2 text-sm",
|
|
@@ -1127,6 +1095,166 @@ function useFileExplorer() {
|
|
|
1127
1095
|
return context;
|
|
1128
1096
|
}
|
|
1129
1097
|
//#endregion
|
|
1098
|
+
//#region src/file-explorer/FilePreview/previewType.ts
|
|
1099
|
+
/** Short-lived so the link doesn't linger; an hour covers a viewing session. */
|
|
1100
|
+
var PREVIEW_URL_EXPIRES_IN = 3600;
|
|
1101
|
+
/** Cap text pulled into the tab; media streams through native elements uncapped. */
|
|
1102
|
+
var CODE_PREVIEW_MAX_BYTES = 10485760;
|
|
1103
|
+
var TABULAR_PREVIEW_MAX_BYTES = 20971520;
|
|
1104
|
+
/** A line cap bounds what a text preview holds and renders; the byte cap above cuts
|
|
1105
|
+
* short whatever the line cap hasn't, and refuses outright only content with no line
|
|
1106
|
+
* break to cut on. Well under Monaco's 20MB and 300K-line thresholds, past which it
|
|
1107
|
+
* stops tokenizing and highlighting dies. */
|
|
1108
|
+
var CODE_PREVIEW_MAX_LINES = 5e3;
|
|
1109
|
+
/** Fetched into a blob URL to render inline regardless of served content type; falls back to download above this. */
|
|
1110
|
+
var PDF_PREVIEW_MAX_BYTES = 52428800;
|
|
1111
|
+
var IMAGE_EXT = /* @__PURE__ */ new Set([
|
|
1112
|
+
"png",
|
|
1113
|
+
"jpg",
|
|
1114
|
+
"jpeg",
|
|
1115
|
+
"gif",
|
|
1116
|
+
"webp",
|
|
1117
|
+
"bmp",
|
|
1118
|
+
"ico",
|
|
1119
|
+
"avif",
|
|
1120
|
+
"svg"
|
|
1121
|
+
]);
|
|
1122
|
+
var VIDEO_EXT = /* @__PURE__ */ new Set([
|
|
1123
|
+
"mp4",
|
|
1124
|
+
"mov",
|
|
1125
|
+
"webm",
|
|
1126
|
+
"m4v",
|
|
1127
|
+
"ogv"
|
|
1128
|
+
]);
|
|
1129
|
+
var AUDIO_EXT = /* @__PURE__ */ new Set([
|
|
1130
|
+
"mp3",
|
|
1131
|
+
"wav",
|
|
1132
|
+
"ogg",
|
|
1133
|
+
"oga",
|
|
1134
|
+
"flac",
|
|
1135
|
+
"m4a",
|
|
1136
|
+
"aac"
|
|
1137
|
+
]);
|
|
1138
|
+
var ARCHIVE_EXT = /* @__PURE__ */ new Set([
|
|
1139
|
+
"zip",
|
|
1140
|
+
"tar",
|
|
1141
|
+
"gz",
|
|
1142
|
+
"tgz",
|
|
1143
|
+
"bz2",
|
|
1144
|
+
"xz",
|
|
1145
|
+
"7z",
|
|
1146
|
+
"rar"
|
|
1147
|
+
]);
|
|
1148
|
+
var LANGUAGE_BY_EXT = {
|
|
1149
|
+
py: "python",
|
|
1150
|
+
ipy: "python",
|
|
1151
|
+
js: "javascript",
|
|
1152
|
+
jsx: "javascript",
|
|
1153
|
+
ts: "typescript",
|
|
1154
|
+
tsx: "typescript",
|
|
1155
|
+
go: "go",
|
|
1156
|
+
rs: "rust",
|
|
1157
|
+
java: "java",
|
|
1158
|
+
c: "c",
|
|
1159
|
+
h: "c",
|
|
1160
|
+
cpp: "cpp",
|
|
1161
|
+
cc: "cpp",
|
|
1162
|
+
hpp: "cpp",
|
|
1163
|
+
cs: "csharp",
|
|
1164
|
+
rb: "ruby",
|
|
1165
|
+
php: "php",
|
|
1166
|
+
swift: "swift",
|
|
1167
|
+
kt: "kotlin",
|
|
1168
|
+
scala: "scala",
|
|
1169
|
+
r: "r",
|
|
1170
|
+
jl: "julia",
|
|
1171
|
+
sh: "shell",
|
|
1172
|
+
bash: "shell",
|
|
1173
|
+
zsh: "shell",
|
|
1174
|
+
fish: "shell",
|
|
1175
|
+
ps1: "powershell",
|
|
1176
|
+
sql: "sql",
|
|
1177
|
+
json: "json",
|
|
1178
|
+
jsonl: "json",
|
|
1179
|
+
yaml: "yaml",
|
|
1180
|
+
yml: "yaml",
|
|
1181
|
+
toml: "ini",
|
|
1182
|
+
ini: "ini",
|
|
1183
|
+
cfg: "ini",
|
|
1184
|
+
conf: "ini",
|
|
1185
|
+
env: "ini",
|
|
1186
|
+
xml: "xml",
|
|
1187
|
+
html: "html",
|
|
1188
|
+
htm: "html",
|
|
1189
|
+
css: "css",
|
|
1190
|
+
scss: "scss",
|
|
1191
|
+
md: "markdown",
|
|
1192
|
+
markdown: "markdown",
|
|
1193
|
+
tex: "plaintext",
|
|
1194
|
+
txt: "plaintext",
|
|
1195
|
+
log: "plaintext",
|
|
1196
|
+
gitignore: "plaintext"
|
|
1197
|
+
};
|
|
1198
|
+
var LANGUAGE_BY_FILENAME = {
|
|
1199
|
+
dockerfile: "dockerfile",
|
|
1200
|
+
makefile: "plaintext"
|
|
1201
|
+
};
|
|
1202
|
+
function getMonacoLanguage(node) {
|
|
1203
|
+
const ext = getFileExtension(node.name);
|
|
1204
|
+
if (ext && LANGUAGE_BY_EXT[ext]) return LANGUAGE_BY_EXT[ext];
|
|
1205
|
+
return LANGUAGE_BY_FILENAME[node.name.toLowerCase()] ?? "plaintext";
|
|
1206
|
+
}
|
|
1207
|
+
function isCodeFile(node) {
|
|
1208
|
+
const ext = getFileExtension(node.name);
|
|
1209
|
+
return ext !== null && ext in LANGUAGE_BY_EXT || node.name.toLowerCase() in LANGUAGE_BY_FILENAME;
|
|
1210
|
+
}
|
|
1211
|
+
function kindFromContentType(ct) {
|
|
1212
|
+
if (ct === "application/pdf") return "pdf";
|
|
1213
|
+
if (ct === "application/x-ipynb+json") return "notebook";
|
|
1214
|
+
if (ct === "text/csv") return "csv";
|
|
1215
|
+
if (ct.startsWith("image/")) return "image";
|
|
1216
|
+
if (ct.startsWith("video/")) return "video";
|
|
1217
|
+
if (ct.startsWith("audio/")) return "audio";
|
|
1218
|
+
if (ct.startsWith("text/") || ct === "application/json" || ct === "application/xml") return "code";
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
function kindFromExtension(ext) {
|
|
1222
|
+
if (ext === "pdf") return "pdf";
|
|
1223
|
+
if (ext === "ipynb") return "notebook";
|
|
1224
|
+
if (ext === "csv" || ext === "tsv") return "csv";
|
|
1225
|
+
if (IMAGE_EXT.has(ext)) return "image";
|
|
1226
|
+
if (VIDEO_EXT.has(ext)) return "video";
|
|
1227
|
+
if (AUDIO_EXT.has(ext)) return "audio";
|
|
1228
|
+
if (ARCHIVE_EXT.has(ext)) return "archive";
|
|
1229
|
+
if (ext in LANGUAGE_BY_EXT) return "code";
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
/** Content type wins when present; extension is the fallback. */
|
|
1233
|
+
function getPreviewKind(node) {
|
|
1234
|
+
if (node.type !== "file") return "unsupported";
|
|
1235
|
+
const ct = (node.contentType ?? "").split(";")[0]?.toLowerCase().trim() ?? "";
|
|
1236
|
+
const ext = getFileExtension(node.name);
|
|
1237
|
+
const ctKind = kindFromContentType(ct);
|
|
1238
|
+
const extKind = ext !== null ? kindFromExtension(ext) : null;
|
|
1239
|
+
if (ctKind === "code" && (extKind === "csv" || extKind === "notebook")) return extKind;
|
|
1240
|
+
return ctKind ?? extKind ?? (isCodeFile(node) ? "code" : "unsupported");
|
|
1241
|
+
}
|
|
1242
|
+
function canPreview(node) {
|
|
1243
|
+
const kind = getPreviewKind(node);
|
|
1244
|
+
return kind !== "unsupported" && kind !== "archive";
|
|
1245
|
+
}
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region src/file-explorer/FilePreview/StreamedBadge.tsx
|
|
1248
|
+
function StreamedBadge() {
|
|
1249
|
+
const t = useStrings().fileExplorer;
|
|
1250
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1251
|
+
className: "inline-flex shrink-0 items-center gap-1 rounded-full border border-green-500/40 bg-green-500/15 px-2 py-0.5 text-[11px] text-green-600",
|
|
1252
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
1253
|
+
"data-tooltip-content": t.preview.streamedTooltip,
|
|
1254
|
+
children: [/* @__PURE__ */ jsx(LockIcon, { className: "h-2.5 w-2.5" }), t.preview.streamed]
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
//#endregion
|
|
1130
1258
|
//#region src/file-explorer/lib/errors.ts
|
|
1131
1259
|
var CorsError = class extends Error {
|
|
1132
1260
|
constructor(message) {
|
|
@@ -1135,70 +1263,236 @@ var CorsError = class extends Error {
|
|
|
1135
1263
|
}
|
|
1136
1264
|
};
|
|
1137
1265
|
//#endregion
|
|
1138
|
-
//#region src/file-explorer/
|
|
1139
|
-
var
|
|
1140
|
-
var
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
{
|
|
1150
|
-
pattern: /^application\/pdf$/,
|
|
1151
|
-
icon: /* @__PURE__ */ jsx(PdfIcon, { className: "h-4 w-4 text-red-500" })
|
|
1152
|
-
},
|
|
1153
|
-
{
|
|
1154
|
-
pattern: /(spreadsheet|csv|excel)/,
|
|
1155
|
-
icon: /* @__PURE__ */ jsx(SpreadsheetIcon, { className: "h-4 w-4 text-green-500" })
|
|
1156
|
-
},
|
|
1157
|
-
{
|
|
1158
|
-
pattern: /(document|word)/,
|
|
1159
|
-
icon: /* @__PURE__ */ jsx(DocumentIcon, { className: "h-4 w-4 text-blue-400" })
|
|
1160
|
-
},
|
|
1161
|
-
{
|
|
1162
|
-
pattern: /(javascript|json|css|html)/,
|
|
1163
|
-
icon: /* @__PURE__ */ jsx(FileTextIcon, { className: "h-4 w-4 text-yellow-500" })
|
|
1164
|
-
},
|
|
1165
|
-
{
|
|
1166
|
-
pattern: /(zip|gzip|tar)/,
|
|
1167
|
-
icon: /* @__PURE__ */ jsx(ZipIcon, { className: "h-4 w-4 text-purple-600" })
|
|
1266
|
+
//#region src/file-explorer/FilePreview/previewCache.ts
|
|
1267
|
+
var MAX_ENTRIES = 2;
|
|
1268
|
+
var urlCache = /* @__PURE__ */ new Map();
|
|
1269
|
+
var contentCache = /* @__PURE__ */ new Map();
|
|
1270
|
+
function trim(cache, onEvict) {
|
|
1271
|
+
while (cache.size > MAX_ENTRIES) {
|
|
1272
|
+
const oldest = cache.keys().next().value;
|
|
1273
|
+
if (oldest === void 0) return;
|
|
1274
|
+
const evicted = cache.get(oldest);
|
|
1275
|
+
cache.delete(oldest);
|
|
1276
|
+
if (evicted !== void 0) onEvict(evicted);
|
|
1168
1277
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
icon: /* @__PURE__ */ jsx(SpreadsheetIcon, { className: "h-4 w-4 text-green-500" })
|
|
1186
|
-
},
|
|
1187
|
-
{
|
|
1188
|
-
pattern: /^(docx?|odt)$/i,
|
|
1189
|
-
icon: /* @__PURE__ */ jsx(DocumentIcon, { className: "h-4 w-4 text-blue-400" })
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
pattern: /^(js|ts|json|css|html|md|txt)$/i,
|
|
1193
|
-
icon: /* @__PURE__ */ jsx(FileTextIcon, { className: "h-4 w-4 text-yellow-500" })
|
|
1194
|
-
},
|
|
1195
|
-
{
|
|
1196
|
-
pattern: /^(zip|gz|gzip|tar|rar)$/i,
|
|
1197
|
-
icon: /* @__PURE__ */ jsx(ZipIcon, { className: "h-4 w-4 text-purple-600" })
|
|
1278
|
+
}
|
|
1279
|
+
function disposeContent(record) {
|
|
1280
|
+
record.controller.abort();
|
|
1281
|
+
if (record.value !== void 0) record.dispose?.(record.value);
|
|
1282
|
+
}
|
|
1283
|
+
function moveToNewest(cache, path, record) {
|
|
1284
|
+
cache.delete(path);
|
|
1285
|
+
cache.set(path, record);
|
|
1286
|
+
}
|
|
1287
|
+
/** Request a presigned URL for `path` once and remember the result; a failed or
|
|
1288
|
+
* empty result is not cached so a later mount retries. */
|
|
1289
|
+
function loadPreviewUrl(path, loader) {
|
|
1290
|
+
const existing = urlCache.get(path);
|
|
1291
|
+
if (existing) {
|
|
1292
|
+
moveToNewest(urlCache, path, existing);
|
|
1293
|
+
return existing;
|
|
1198
1294
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1295
|
+
const record = { promise: loader() };
|
|
1296
|
+
record.promise.then((value) => {
|
|
1297
|
+
if (value[0] && !value[1]) record.value = value;
|
|
1298
|
+
else if (urlCache.get(path) === record) urlCache.delete(path);
|
|
1299
|
+
}).catch(() => {
|
|
1300
|
+
if (urlCache.get(path) === record) urlCache.delete(path);
|
|
1301
|
+
});
|
|
1302
|
+
urlCache.set(path, record);
|
|
1303
|
+
trim(urlCache, () => {});
|
|
1304
|
+
return record;
|
|
1305
|
+
}
|
|
1306
|
+
/** Fetch the object for `path` once and remember it; `dispose` frees the value
|
|
1307
|
+
* (e.g. revokes a blob URL) when the entry is evicted. A rejected fetch is not
|
|
1308
|
+
* cached so a later mount retries. */
|
|
1309
|
+
function loadPreviewContent(path, loader, dispose) {
|
|
1310
|
+
const existing = contentCache.get(path);
|
|
1311
|
+
if (existing) {
|
|
1312
|
+
moveToNewest(contentCache, path, existing);
|
|
1313
|
+
return existing;
|
|
1314
|
+
}
|
|
1315
|
+
const controller = new AbortController();
|
|
1316
|
+
const record = {
|
|
1317
|
+
promise: loader(controller.signal),
|
|
1318
|
+
controller,
|
|
1319
|
+
dispose
|
|
1320
|
+
};
|
|
1321
|
+
record.promise.then((value) => {
|
|
1322
|
+
record.value = value;
|
|
1323
|
+
}).catch(() => {
|
|
1324
|
+
if (contentCache.get(path) === record) contentCache.delete(path);
|
|
1325
|
+
});
|
|
1326
|
+
contentCache.set(path, record);
|
|
1327
|
+
trim(contentCache, disposeContent);
|
|
1328
|
+
return record;
|
|
1329
|
+
}
|
|
1330
|
+
/** Fetch a preview object over its signed URL; a 401/403 means the URL is stale
|
|
1331
|
+
* or unauthorized, so drop it from the cache and let the next mount fetch a fresh
|
|
1332
|
+
* one instead of replaying the dead one. */
|
|
1333
|
+
async function fetchPreviewObject(path, url, signal, headers) {
|
|
1334
|
+
let response;
|
|
1335
|
+
try {
|
|
1336
|
+
response = await fetch(url, {
|
|
1337
|
+
signal,
|
|
1338
|
+
...headers ? { headers } : {}
|
|
1339
|
+
});
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
if (error instanceof TypeError) throw new CorsError();
|
|
1342
|
+
throw error;
|
|
1343
|
+
}
|
|
1344
|
+
if (!response.ok) {
|
|
1345
|
+
if (response.status === 401 || response.status === 403) urlCache.delete(path);
|
|
1346
|
+
throw new Error(`Failed to load file (${response.status})`);
|
|
1347
|
+
}
|
|
1348
|
+
return response;
|
|
1349
|
+
}
|
|
1350
|
+
function peekPreviewUrl(path) {
|
|
1351
|
+
return urlCache.get(path)?.value;
|
|
1352
|
+
}
|
|
1353
|
+
function peekPreviewContent(path) {
|
|
1354
|
+
return contentCache.get(path)?.value;
|
|
1355
|
+
}
|
|
1356
|
+
function clearPreviewCache() {
|
|
1357
|
+
for (const record of contentCache.values()) disposeContent(record);
|
|
1358
|
+
contentCache.clear();
|
|
1359
|
+
urlCache.clear();
|
|
1360
|
+
}
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region src/file-explorer/FilePreview/usePresignedUrl.ts
|
|
1363
|
+
function toState$2([url, error], linkErrorMessage) {
|
|
1364
|
+
if (error || !url) return {
|
|
1365
|
+
url: null,
|
|
1366
|
+
loading: false,
|
|
1367
|
+
error: error?.message ?? linkErrorMessage
|
|
1368
|
+
};
|
|
1369
|
+
return {
|
|
1370
|
+
url,
|
|
1371
|
+
loading: false,
|
|
1372
|
+
error: null
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
/** Fetch a presigned URL per file, cached so repeated mounts share one request; callbacks come from refs so the effect keys only on file identity. */
|
|
1376
|
+
function usePresignedUrl(node, getPresignedUrl, { enabled, linkErrorMessage }) {
|
|
1377
|
+
const path = enabled ? node?.path ?? null : null;
|
|
1378
|
+
const [state, setState] = useState({
|
|
1379
|
+
url: null,
|
|
1380
|
+
loading: false,
|
|
1381
|
+
error: null
|
|
1382
|
+
});
|
|
1383
|
+
const [statePath, setStatePath] = useState(path);
|
|
1384
|
+
if (statePath !== path) {
|
|
1385
|
+
setStatePath(path);
|
|
1386
|
+
setState({
|
|
1387
|
+
url: null,
|
|
1388
|
+
loading: path !== null,
|
|
1389
|
+
error: null
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
const getUrlRef = useRef(getPresignedUrl);
|
|
1393
|
+
getUrlRef.current = getPresignedUrl;
|
|
1394
|
+
const linkErrorRef = useRef(linkErrorMessage);
|
|
1395
|
+
linkErrorRef.current = linkErrorMessage;
|
|
1396
|
+
const nodeRef = useRef(node);
|
|
1397
|
+
nodeRef.current = node;
|
|
1398
|
+
useEffect(() => {
|
|
1399
|
+
const node = nodeRef.current;
|
|
1400
|
+
if (!path || !node) {
|
|
1401
|
+
setState({
|
|
1402
|
+
url: null,
|
|
1403
|
+
loading: false,
|
|
1404
|
+
error: null
|
|
1405
|
+
});
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
let cancelled = false;
|
|
1409
|
+
const record = loadPreviewUrl(path, () => getUrlRef.current(node));
|
|
1410
|
+
setState(record.value ? toState$2(record.value, linkErrorRef.current) : {
|
|
1411
|
+
url: null,
|
|
1412
|
+
loading: true,
|
|
1413
|
+
error: null
|
|
1414
|
+
});
|
|
1415
|
+
record.promise.then((result) => {
|
|
1416
|
+
if (!cancelled) setState(toState$2(result, linkErrorRef.current));
|
|
1417
|
+
}).catch((err) => {
|
|
1418
|
+
if (!cancelled) setState({
|
|
1419
|
+
url: null,
|
|
1420
|
+
loading: false,
|
|
1421
|
+
error: err instanceof Error ? err.message : linkErrorRef.current
|
|
1422
|
+
});
|
|
1423
|
+
});
|
|
1424
|
+
return () => {
|
|
1425
|
+
cancelled = true;
|
|
1426
|
+
};
|
|
1427
|
+
}, [path]);
|
|
1428
|
+
const cached = path ? peekPreviewUrl(path) : void 0;
|
|
1429
|
+
return cached ? toState$2(cached, linkErrorRef.current) : state;
|
|
1430
|
+
}
|
|
1431
|
+
//#endregion
|
|
1432
|
+
//#region src/file-explorer/helpers.tsx
|
|
1433
|
+
var SIDEBAR_WIDTH_STORAGE_KEY = "fileExplorerSidebarWidth";
|
|
1434
|
+
var contentTypeIconMap = [
|
|
1435
|
+
{
|
|
1436
|
+
pattern: /^image\//,
|
|
1437
|
+
icon: /* @__PURE__ */ jsx(ImageIcon, { className: "h-4 w-4 text-blue-500" })
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
pattern: /^video\//,
|
|
1441
|
+
icon: /* @__PURE__ */ jsx(VideoIcon, { className: "h-4 w-4 text-purple-500" })
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
pattern: /^application\/pdf$/,
|
|
1445
|
+
icon: /* @__PURE__ */ jsx(PdfIcon, { className: "h-4 w-4 text-red-500" })
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
pattern: /(spreadsheet|csv|excel)/,
|
|
1449
|
+
icon: /* @__PURE__ */ jsx(SpreadsheetIcon, { className: "h-4 w-4 text-green-500" })
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
pattern: /(document|word)/,
|
|
1453
|
+
icon: /* @__PURE__ */ jsx(DocumentIcon, { className: "h-4 w-4 text-blue-400" })
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
pattern: /(javascript|json|css|html)/,
|
|
1457
|
+
icon: /* @__PURE__ */ jsx(FileTextIcon, { className: "h-4 w-4 text-yellow-500" })
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
pattern: /(zip|gzip|tar)/,
|
|
1461
|
+
icon: /* @__PURE__ */ jsx(ZipIcon, { className: "h-4 w-4 text-purple-600" })
|
|
1462
|
+
}
|
|
1463
|
+
];
|
|
1464
|
+
var extensionIconMap = [
|
|
1465
|
+
{
|
|
1466
|
+
pattern: /^(jpe?g|png|gif|svg|webp)$/i,
|
|
1467
|
+
icon: /* @__PURE__ */ jsx(ImageIcon, { className: "h-4 w-4 text-blue-500" })
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
pattern: /^(mp4|mov|avi|mkv)$/i,
|
|
1471
|
+
icon: /* @__PURE__ */ jsx(VideoIcon, { className: "h-4 w-4 text-purple-500" })
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
pattern: /^pdf$/i,
|
|
1475
|
+
icon: /* @__PURE__ */ jsx(PdfIcon, { className: "h-4 w-4 text-red-500" })
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
pattern: /^(xlsx?|csv|ods)$/i,
|
|
1479
|
+
icon: /* @__PURE__ */ jsx(SpreadsheetIcon, { className: "h-4 w-4 text-green-500" })
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
pattern: /^(docx?|odt)$/i,
|
|
1483
|
+
icon: /* @__PURE__ */ jsx(DocumentIcon, { className: "h-4 w-4 text-blue-400" })
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
pattern: /^(js|ts|json|css|html|md|txt)$/i,
|
|
1487
|
+
icon: /* @__PURE__ */ jsx(FileTextIcon, { className: "h-4 w-4 text-yellow-500" })
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
pattern: /^(zip|gz|gzip|tar|rar)$/i,
|
|
1491
|
+
icon: /* @__PURE__ */ jsx(ZipIcon, { className: "h-4 w-4 text-purple-600" })
|
|
1492
|
+
}
|
|
1493
|
+
];
|
|
1494
|
+
function getNodeIcon(node, storageIconUrl, storageIconAlt = "Storage icon") {
|
|
1495
|
+
const isUserNode = !node.storageId;
|
|
1202
1496
|
const isStorageNode = node.root;
|
|
1203
1497
|
if (isUserNode) return /* @__PURE__ */ jsx(UserIcon, { className: "h-3 w-3 mr-2" });
|
|
1204
1498
|
if (isStorageNode && node.storageType === "workspace") return /* @__PURE__ */ jsx(DiskIcon, { className: "h-4 w-4 mr-2" });
|
|
@@ -1577,7 +1871,7 @@ function SkeletonPreview() {
|
|
|
1577
1871
|
})
|
|
1578
1872
|
}), /* @__PURE__ */ jsx("div", {
|
|
1579
1873
|
className: "overflow-auto flex-1 px-2 py-2 space-y-1.5",
|
|
1580
|
-
children:
|
|
1874
|
+
children: positionKeys(6, "skeleton").map((key) => /* @__PURE__ */ jsx(Skeleton, { height: 14 }, key))
|
|
1581
1875
|
})]
|
|
1582
1876
|
})
|
|
1583
1877
|
}), /* @__PURE__ */ jsxs("div", {
|
|
@@ -1618,493 +1912,45 @@ function ShareFileButton({ node, onShare }) {
|
|
|
1618
1912
|
});
|
|
1619
1913
|
},
|
|
1620
1914
|
disabled: isPending,
|
|
1621
|
-
children: [/* @__PURE__ */ jsx(ShareIcon, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
1622
|
-
className: "hidden md:inline",
|
|
1623
|
-
children: t.empty.shareThisFile
|
|
1624
|
-
})]
|
|
1625
|
-
});
|
|
1626
|
-
}
|
|
1627
|
-
async function addCorsRules({ provision, notify, strings, csp, type, username, storageName, onRefresh }) {
|
|
1628
|
-
if (!storageName || !username || !csp) {
|
|
1629
|
-
notify.error(strings.corsNoStorage);
|
|
1630
|
-
return;
|
|
1631
|
-
}
|
|
1632
|
-
const res = await provision({
|
|
1633
|
-
csp,
|
|
1634
|
-
type,
|
|
1635
|
-
username,
|
|
1636
|
-
storageName
|
|
1637
|
-
});
|
|
1638
|
-
if (res.error) {
|
|
1639
|
-
notify.error(res.error);
|
|
1640
|
-
return;
|
|
1641
|
-
}
|
|
1642
|
-
if (!onRefresh) {
|
|
1643
|
-
notify.success(strings.corsAdded);
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
const pollBudgetMs = 9e4;
|
|
1647
|
-
const pollIntervalMs = 3e3;
|
|
1648
|
-
const deadline = Date.now() + pollBudgetMs;
|
|
1649
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1650
|
-
while (Date.now() < deadline) {
|
|
1651
|
-
const result = await onRefresh({ silent: true });
|
|
1652
|
-
if (!result || !result.corsError) {
|
|
1653
|
-
await onRefresh();
|
|
1654
|
-
notify.success(strings.corsAdded);
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1658
|
-
}
|
|
1659
|
-
notify.warning(strings.corsPropagating);
|
|
1660
|
-
}
|
|
1661
|
-
//#endregion
|
|
1662
|
-
//#region src/file-explorer/lib/paging.ts
|
|
1663
|
-
/** Both providers already cap an unqualified list at 1,000, so stating it leaves
|
|
1664
|
-
* single-page directories byte-identical. */
|
|
1665
|
-
var LIST_PAGE_SIZE = 1e3;
|
|
1666
|
-
function isListed(listings, path) {
|
|
1667
|
-
return (listings.get(path)?.pagesLoaded ?? 0) > 0;
|
|
1668
|
-
}
|
|
1669
|
-
/** True even while a request is in flight, so the sentinel stays put. */
|
|
1670
|
-
function hasMore(listings, path) {
|
|
1671
|
-
return !!listings.get(path)?.cursor;
|
|
1672
|
-
}
|
|
1673
|
-
/** A failed page needs an explicit retry, so it stops auto-loading here. */
|
|
1674
|
-
function canLoadMore(listings, path) {
|
|
1675
|
-
const listing = listings.get(path);
|
|
1676
|
-
return !!listing?.cursor && !listing.loadingMore && !listing.error;
|
|
1677
|
-
}
|
|
1678
|
-
/** Auto-load stops at the cap; past it the user asks for each page. */
|
|
1679
|
-
function canAutoLoadMore(listings, path) {
|
|
1680
|
-
return canLoadMore(listings, path) && (listings.get(path)?.pagesLoaded ?? 0) < 5;
|
|
1681
|
-
}
|
|
1682
|
-
function isLoadingMore(listings, path) {
|
|
1683
|
-
return listings.get(path)?.loadingMore === true;
|
|
1684
|
-
}
|
|
1685
|
-
function loadMoreError(listings, path) {
|
|
1686
|
-
return listings.get(path)?.error;
|
|
1687
|
-
}
|
|
1688
|
-
function beginLoadingMore(listings, path) {
|
|
1689
|
-
const listing = listings.get(path);
|
|
1690
|
-
if (!listing) return listings;
|
|
1691
|
-
return new Map(listings).set(path, {
|
|
1692
|
-
...listing,
|
|
1693
|
-
loadingMore: true,
|
|
1694
|
-
error: void 0
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
function advanceListing(listings, path, page) {
|
|
1698
|
-
const listing = listings.get(path);
|
|
1699
|
-
return new Map(listings).set(path, {
|
|
1700
|
-
cursor: page.cursor,
|
|
1701
|
-
pagesLoaded: (listing?.pagesLoaded ?? 0) + 1,
|
|
1702
|
-
loadingMore: false,
|
|
1703
|
-
entries: [...listing?.entries ?? [], ...page.entries]
|
|
1704
|
-
});
|
|
1705
|
-
}
|
|
1706
|
-
/** Subfolders the page no longer names lose their records, or a re-created folder
|
|
1707
|
-
* would show old contents; the rest keep theirs, since a refresh lists them
|
|
1708
|
-
* alongside this one and a parent landing later must not undo a child's page. */
|
|
1709
|
-
function replaceListing(listings, path, page) {
|
|
1710
|
-
const prefix = ensureTrailingSlash(path);
|
|
1711
|
-
const subfolders = new Set(page.entries.filter((entry) => entry.type === "directory").map(getNodeName));
|
|
1712
|
-
const next = new Map(listings);
|
|
1713
|
-
next.delete(path);
|
|
1714
|
-
for (const key of listings.keys()) {
|
|
1715
|
-
if (!key.startsWith(prefix)) continue;
|
|
1716
|
-
if (!subfolders.has(key.slice(prefix.length).split("/")[0] ?? "")) next.delete(key);
|
|
1717
|
-
}
|
|
1718
|
-
return advanceListing(next, path, page);
|
|
1719
|
-
}
|
|
1720
|
-
/** `pagesLoaded` stays put, so a failed first page still reads as unlisted. */
|
|
1721
|
-
function recordListingError(listings, path, error) {
|
|
1722
|
-
const listing = listings.get(path);
|
|
1723
|
-
return new Map(listings).set(path, {
|
|
1724
|
-
cursor: listing?.cursor,
|
|
1725
|
-
pagesLoaded: listing?.pagesLoaded ?? 0,
|
|
1726
|
-
loadingMore: false,
|
|
1727
|
-
error,
|
|
1728
|
-
entries: listing?.entries ?? []
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
/** Keeps the cursor and pages, so a retry resumes paging instead of starting over. */
|
|
1732
|
-
function clearListingError(listings, path) {
|
|
1733
|
-
const listing = listings.get(path);
|
|
1734
|
-
if (!listing) return listings;
|
|
1735
|
-
return new Map(listings).set(path, {
|
|
1736
|
-
...listing,
|
|
1737
|
-
error: void 0
|
|
1738
|
-
});
|
|
1739
|
-
}
|
|
1740
|
-
function clearListingsUnder(listings, path) {
|
|
1741
|
-
const prefix = ensureTrailingSlash(path);
|
|
1742
|
-
const next = new Map(listings);
|
|
1743
|
-
for (const key of listings.keys()) if (key === path || key.startsWith(prefix)) next.delete(key);
|
|
1744
|
-
return next;
|
|
1745
|
-
}
|
|
1746
|
-
/** Tree paths whose children should keep arrival order, since sorting them would
|
|
1747
|
-
* reshuffle rows under the user mid-scroll. */
|
|
1748
|
-
function incompletePaths(listings) {
|
|
1749
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1750
|
-
for (const [path, listing] of listings) if (listing.cursor) paths.add(path);
|
|
1751
|
-
return paths;
|
|
1752
|
-
}
|
|
1753
|
-
/** What a deep link needs next. Pure so the walk is table-testable. */
|
|
1754
|
-
function nextHydrationStep({ targetResolved, ancestors, isListableDir, listings, inFlight, pagesPulled }) {
|
|
1755
|
-
for (const path of ancestors) {
|
|
1756
|
-
if (!isListableDir(path)) continue;
|
|
1757
|
-
if (inFlight.has(path)) return { kind: "done" };
|
|
1758
|
-
if (!listings.has(path)) return {
|
|
1759
|
-
kind: "fetch",
|
|
1760
|
-
path
|
|
1761
|
-
};
|
|
1762
|
-
}
|
|
1763
|
-
if (targetResolved) return { kind: "done" };
|
|
1764
|
-
const deepest = [...ancestors].reverse().find(isListableDir);
|
|
1765
|
-
if (!deepest) return { kind: "done" };
|
|
1766
|
-
if (!hasMore(listings, deepest)) return {
|
|
1767
|
-
kind: "stuck",
|
|
1768
|
-
path: deepest
|
|
1769
|
-
};
|
|
1770
|
-
if (loadMoreError(listings, deepest)) return {
|
|
1771
|
-
kind: "stuck",
|
|
1772
|
-
path: deepest
|
|
1773
|
-
};
|
|
1774
|
-
if (pagesPulled >= 20) return {
|
|
1775
|
-
kind: "stuck",
|
|
1776
|
-
path: deepest
|
|
1777
|
-
};
|
|
1778
|
-
return isLoadingMore(listings, deepest) ? { kind: "done" } : {
|
|
1779
|
-
kind: "more",
|
|
1780
|
-
path: deepest
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
//#endregion
|
|
1784
|
-
//#region src/file-explorer/FilePreview/StreamedBadge.tsx
|
|
1785
|
-
function StreamedBadge() {
|
|
1786
|
-
const t = useStrings().fileExplorer;
|
|
1787
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
1788
|
-
className: "inline-flex shrink-0 items-center gap-1 rounded-full border border-green-500/40 bg-green-500/15 px-2 py-0.5 text-[11px] text-green-600",
|
|
1789
|
-
"data-tooltip-id": TOOLTIP_ID,
|
|
1790
|
-
"data-tooltip-content": t.preview.streamedTooltip,
|
|
1791
|
-
children: [/* @__PURE__ */ jsx(LockIcon, { className: "h-2.5 w-2.5" }), t.preview.streamed]
|
|
1792
|
-
});
|
|
1793
|
-
}
|
|
1794
|
-
//#endregion
|
|
1795
|
-
//#region src/file-explorer/FilePreview/previewType.ts
|
|
1796
|
-
/** Short-lived so the link doesn't linger; an hour covers a viewing session. */
|
|
1797
|
-
var PREVIEW_URL_EXPIRES_IN = 3600;
|
|
1798
|
-
/** Cap text pulled into the tab; media streams through native elements uncapped. */
|
|
1799
|
-
var CODE_PREVIEW_MAX_BYTES = 10 * 1024 * 1024;
|
|
1800
|
-
var TABULAR_PREVIEW_MAX_BYTES = 20 * 1024 * 1024;
|
|
1801
|
-
/** A line cap bounds what a text preview holds and renders; the byte cap above cuts
|
|
1802
|
-
* short whatever the line cap hasn't, and refuses outright only content with no line
|
|
1803
|
-
* break to cut on. Well under Monaco's 20MB and 300K-line thresholds, past which it
|
|
1804
|
-
* stops tokenizing and highlighting dies. */
|
|
1805
|
-
var CODE_PREVIEW_MAX_LINES = 5e3;
|
|
1806
|
-
/** Fetched into a blob URL to render inline regardless of served content type; falls back to download above this. */
|
|
1807
|
-
var PDF_PREVIEW_MAX_BYTES = 50 * 1024 * 1024;
|
|
1808
|
-
var IMAGE_EXT = new Set([
|
|
1809
|
-
"png",
|
|
1810
|
-
"jpg",
|
|
1811
|
-
"jpeg",
|
|
1812
|
-
"gif",
|
|
1813
|
-
"webp",
|
|
1814
|
-
"bmp",
|
|
1815
|
-
"ico",
|
|
1816
|
-
"avif",
|
|
1817
|
-
"svg"
|
|
1818
|
-
]);
|
|
1819
|
-
var VIDEO_EXT = new Set([
|
|
1820
|
-
"mp4",
|
|
1821
|
-
"mov",
|
|
1822
|
-
"webm",
|
|
1823
|
-
"m4v",
|
|
1824
|
-
"ogv"
|
|
1825
|
-
]);
|
|
1826
|
-
var AUDIO_EXT = new Set([
|
|
1827
|
-
"mp3",
|
|
1828
|
-
"wav",
|
|
1829
|
-
"ogg",
|
|
1830
|
-
"oga",
|
|
1831
|
-
"flac",
|
|
1832
|
-
"m4a",
|
|
1833
|
-
"aac"
|
|
1834
|
-
]);
|
|
1835
|
-
var ARCHIVE_EXT = new Set([
|
|
1836
|
-
"zip",
|
|
1837
|
-
"tar",
|
|
1838
|
-
"gz",
|
|
1839
|
-
"tgz",
|
|
1840
|
-
"bz2",
|
|
1841
|
-
"xz",
|
|
1842
|
-
"7z",
|
|
1843
|
-
"rar"
|
|
1844
|
-
]);
|
|
1845
|
-
var LANGUAGE_BY_EXT = {
|
|
1846
|
-
py: "python",
|
|
1847
|
-
ipy: "python",
|
|
1848
|
-
js: "javascript",
|
|
1849
|
-
jsx: "javascript",
|
|
1850
|
-
ts: "typescript",
|
|
1851
|
-
tsx: "typescript",
|
|
1852
|
-
go: "go",
|
|
1853
|
-
rs: "rust",
|
|
1854
|
-
java: "java",
|
|
1855
|
-
c: "c",
|
|
1856
|
-
h: "c",
|
|
1857
|
-
cpp: "cpp",
|
|
1858
|
-
cc: "cpp",
|
|
1859
|
-
hpp: "cpp",
|
|
1860
|
-
cs: "csharp",
|
|
1861
|
-
rb: "ruby",
|
|
1862
|
-
php: "php",
|
|
1863
|
-
swift: "swift",
|
|
1864
|
-
kt: "kotlin",
|
|
1865
|
-
scala: "scala",
|
|
1866
|
-
r: "r",
|
|
1867
|
-
jl: "julia",
|
|
1868
|
-
sh: "shell",
|
|
1869
|
-
bash: "shell",
|
|
1870
|
-
zsh: "shell",
|
|
1871
|
-
fish: "shell",
|
|
1872
|
-
ps1: "powershell",
|
|
1873
|
-
sql: "sql",
|
|
1874
|
-
json: "json",
|
|
1875
|
-
jsonl: "json",
|
|
1876
|
-
yaml: "yaml",
|
|
1877
|
-
yml: "yaml",
|
|
1878
|
-
toml: "ini",
|
|
1879
|
-
ini: "ini",
|
|
1880
|
-
cfg: "ini",
|
|
1881
|
-
conf: "ini",
|
|
1882
|
-
env: "ini",
|
|
1883
|
-
xml: "xml",
|
|
1884
|
-
html: "html",
|
|
1885
|
-
htm: "html",
|
|
1886
|
-
css: "css",
|
|
1887
|
-
scss: "scss",
|
|
1888
|
-
md: "markdown",
|
|
1889
|
-
markdown: "markdown",
|
|
1890
|
-
tex: "plaintext",
|
|
1891
|
-
txt: "plaintext",
|
|
1892
|
-
log: "plaintext",
|
|
1893
|
-
gitignore: "plaintext"
|
|
1894
|
-
};
|
|
1895
|
-
var LANGUAGE_BY_FILENAME = {
|
|
1896
|
-
dockerfile: "dockerfile",
|
|
1897
|
-
makefile: "plaintext"
|
|
1898
|
-
};
|
|
1899
|
-
function getMonacoLanguage(node) {
|
|
1900
|
-
const ext = getFileExtension(node.name);
|
|
1901
|
-
if (ext && LANGUAGE_BY_EXT[ext]) return LANGUAGE_BY_EXT[ext];
|
|
1902
|
-
return LANGUAGE_BY_FILENAME[node.name.toLowerCase()] ?? "plaintext";
|
|
1903
|
-
}
|
|
1904
|
-
function isCodeFile(node) {
|
|
1905
|
-
const ext = getFileExtension(node.name);
|
|
1906
|
-
return ext !== null && ext in LANGUAGE_BY_EXT || node.name.toLowerCase() in LANGUAGE_BY_FILENAME;
|
|
1907
|
-
}
|
|
1908
|
-
function kindFromContentType(ct) {
|
|
1909
|
-
if (ct === "application/pdf") return "pdf";
|
|
1910
|
-
if (ct === "application/x-ipynb+json") return "notebook";
|
|
1911
|
-
if (ct === "text/csv") return "csv";
|
|
1912
|
-
if (ct.startsWith("image/")) return "image";
|
|
1913
|
-
if (ct.startsWith("video/")) return "video";
|
|
1914
|
-
if (ct.startsWith("audio/")) return "audio";
|
|
1915
|
-
if (ct.startsWith("text/") || ct === "application/json" || ct === "application/xml") return "code";
|
|
1916
|
-
return null;
|
|
1917
|
-
}
|
|
1918
|
-
function kindFromExtension(ext) {
|
|
1919
|
-
if (ext === "pdf") return "pdf";
|
|
1920
|
-
if (ext === "ipynb") return "notebook";
|
|
1921
|
-
if (ext === "csv" || ext === "tsv") return "csv";
|
|
1922
|
-
if (IMAGE_EXT.has(ext)) return "image";
|
|
1923
|
-
if (VIDEO_EXT.has(ext)) return "video";
|
|
1924
|
-
if (AUDIO_EXT.has(ext)) return "audio";
|
|
1925
|
-
if (ARCHIVE_EXT.has(ext)) return "archive";
|
|
1926
|
-
if (ext in LANGUAGE_BY_EXT) return "code";
|
|
1927
|
-
return null;
|
|
1928
|
-
}
|
|
1929
|
-
/** Content type wins when present; extension is the fallback. */
|
|
1930
|
-
function getPreviewKind(node) {
|
|
1931
|
-
if (node.type !== "file") return "unsupported";
|
|
1932
|
-
const ct = (node.contentType ?? "").split(";")[0]?.toLowerCase().trim() ?? "";
|
|
1933
|
-
const ext = getFileExtension(node.name);
|
|
1934
|
-
const ctKind = kindFromContentType(ct);
|
|
1935
|
-
const extKind = ext !== null ? kindFromExtension(ext) : null;
|
|
1936
|
-
if (ctKind === "code" && (extKind === "csv" || extKind === "notebook")) return extKind;
|
|
1937
|
-
return ctKind ?? extKind ?? (isCodeFile(node) ? "code" : "unsupported");
|
|
1938
|
-
}
|
|
1939
|
-
function canPreview(node) {
|
|
1940
|
-
const kind = getPreviewKind(node);
|
|
1941
|
-
return kind !== "unsupported" && kind !== "archive";
|
|
1942
|
-
}
|
|
1943
|
-
//#endregion
|
|
1944
|
-
//#region src/file-explorer/FilePreview/previewCache.ts
|
|
1945
|
-
var MAX_ENTRIES = 2;
|
|
1946
|
-
var urlCache = /* @__PURE__ */ new Map();
|
|
1947
|
-
var contentCache = /* @__PURE__ */ new Map();
|
|
1948
|
-
function trim(cache, onEvict) {
|
|
1949
|
-
while (cache.size > MAX_ENTRIES) {
|
|
1950
|
-
const oldest = cache.keys().next().value;
|
|
1951
|
-
if (oldest === void 0) return;
|
|
1952
|
-
const evicted = cache.get(oldest);
|
|
1953
|
-
cache.delete(oldest);
|
|
1954
|
-
if (evicted !== void 0) onEvict(evicted);
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
function disposeContent(record) {
|
|
1958
|
-
record.controller.abort();
|
|
1959
|
-
if (record.value !== void 0) record.dispose?.(record.value);
|
|
1960
|
-
}
|
|
1961
|
-
function moveToNewest(cache, path, record) {
|
|
1962
|
-
cache.delete(path);
|
|
1963
|
-
cache.set(path, record);
|
|
1964
|
-
}
|
|
1965
|
-
/** Request a presigned URL for `path` once and remember the result; a failed or
|
|
1966
|
-
* empty result is not cached so a later mount retries. */
|
|
1967
|
-
function loadPreviewUrl(path, loader) {
|
|
1968
|
-
const existing = urlCache.get(path);
|
|
1969
|
-
if (existing) {
|
|
1970
|
-
moveToNewest(urlCache, path, existing);
|
|
1971
|
-
return existing;
|
|
1972
|
-
}
|
|
1973
|
-
const record = { promise: loader() };
|
|
1974
|
-
record.promise.then((value) => {
|
|
1975
|
-
if (value[0] && !value[1]) record.value = value;
|
|
1976
|
-
else if (urlCache.get(path) === record) urlCache.delete(path);
|
|
1977
|
-
}).catch(() => {
|
|
1978
|
-
if (urlCache.get(path) === record) urlCache.delete(path);
|
|
1979
|
-
});
|
|
1980
|
-
urlCache.set(path, record);
|
|
1981
|
-
trim(urlCache, () => {});
|
|
1982
|
-
return record;
|
|
1983
|
-
}
|
|
1984
|
-
/** Fetch the object for `path` once and remember it; `dispose` frees the value
|
|
1985
|
-
* (e.g. revokes a blob URL) when the entry is evicted. A rejected fetch is not
|
|
1986
|
-
* cached so a later mount retries. */
|
|
1987
|
-
function loadPreviewContent(path, loader, dispose) {
|
|
1988
|
-
const existing = contentCache.get(path);
|
|
1989
|
-
if (existing) {
|
|
1990
|
-
moveToNewest(contentCache, path, existing);
|
|
1991
|
-
return existing;
|
|
1992
|
-
}
|
|
1993
|
-
const controller = new AbortController();
|
|
1994
|
-
const record = {
|
|
1995
|
-
promise: loader(controller.signal),
|
|
1996
|
-
controller,
|
|
1997
|
-
dispose
|
|
1998
|
-
};
|
|
1999
|
-
record.promise.then((value) => {
|
|
2000
|
-
record.value = value;
|
|
2001
|
-
}).catch(() => {
|
|
2002
|
-
if (contentCache.get(path) === record) contentCache.delete(path);
|
|
1915
|
+
children: [/* @__PURE__ */ jsx(ShareIcon, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
1916
|
+
className: "hidden md:inline",
|
|
1917
|
+
children: t.empty.shareThisFile
|
|
1918
|
+
})]
|
|
2003
1919
|
});
|
|
2004
|
-
contentCache.set(path, record);
|
|
2005
|
-
trim(contentCache, disposeContent);
|
|
2006
|
-
return record;
|
|
2007
1920
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
let response;
|
|
2013
|
-
try {
|
|
2014
|
-
response = await fetch(url, {
|
|
2015
|
-
signal,
|
|
2016
|
-
...headers ? { headers } : {}
|
|
2017
|
-
});
|
|
2018
|
-
} catch (error) {
|
|
2019
|
-
if (error instanceof TypeError) throw new CorsError();
|
|
2020
|
-
throw error;
|
|
2021
|
-
}
|
|
2022
|
-
if (!response.ok) {
|
|
2023
|
-
if (response.status === 401 || response.status === 403) urlCache.delete(path);
|
|
2024
|
-
throw new Error(`Failed to load file (${response.status})`);
|
|
1921
|
+
async function addCorsRules({ provision, notify, strings, csp, type, username, storageName, onRefresh }) {
|
|
1922
|
+
if (!storageName || !username || !csp) {
|
|
1923
|
+
notify.error(strings.corsNoStorage);
|
|
1924
|
+
return;
|
|
2025
1925
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
function peekPreviewContent(path) {
|
|
2032
|
-
return contentCache.get(path)?.value;
|
|
2033
|
-
}
|
|
2034
|
-
function clearPreviewCache() {
|
|
2035
|
-
for (const record of contentCache.values()) disposeContent(record);
|
|
2036
|
-
contentCache.clear();
|
|
2037
|
-
urlCache.clear();
|
|
2038
|
-
}
|
|
2039
|
-
//#endregion
|
|
2040
|
-
//#region src/file-explorer/FilePreview/usePresignedUrl.ts
|
|
2041
|
-
function toState$2([url, error], linkErrorMessage) {
|
|
2042
|
-
if (error || !url) return {
|
|
2043
|
-
url: null,
|
|
2044
|
-
loading: false,
|
|
2045
|
-
error: error?.message ?? linkErrorMessage
|
|
2046
|
-
};
|
|
2047
|
-
return {
|
|
2048
|
-
url,
|
|
2049
|
-
loading: false,
|
|
2050
|
-
error: null
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
/** Fetch a presigned URL per file, cached so repeated mounts share one request; callbacks come from refs so the effect keys only on file identity. */
|
|
2054
|
-
function usePresignedUrl(node, getPresignedUrl, { enabled, linkErrorMessage }) {
|
|
2055
|
-
const path = enabled ? node?.path ?? null : null;
|
|
2056
|
-
const [state, setState] = useState({
|
|
2057
|
-
url: null,
|
|
2058
|
-
loading: false,
|
|
2059
|
-
error: null
|
|
1926
|
+
const res = await provision({
|
|
1927
|
+
csp,
|
|
1928
|
+
type,
|
|
1929
|
+
username,
|
|
1930
|
+
storageName
|
|
2060
1931
|
});
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
setState({
|
|
2065
|
-
url: null,
|
|
2066
|
-
loading: path !== null,
|
|
2067
|
-
error: null
|
|
2068
|
-
});
|
|
1932
|
+
if (res.error) {
|
|
1933
|
+
notify.error(res.error);
|
|
1934
|
+
return;
|
|
2069
1935
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
});
|
|
1936
|
+
if (!onRefresh) {
|
|
1937
|
+
notify.success(strings.corsAdded);
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const pollBudgetMs = 9e4;
|
|
1941
|
+
const pollIntervalMs = 3e3;
|
|
1942
|
+
const deadline = Date.now() + pollBudgetMs;
|
|
1943
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1944
|
+
while (Date.now() < deadline) {
|
|
1945
|
+
const result = await onRefresh({ silent: true });
|
|
1946
|
+
if (!result || !result.corsError) {
|
|
1947
|
+
await onRefresh();
|
|
1948
|
+
notify.success(strings.corsAdded);
|
|
2084
1949
|
return;
|
|
2085
1950
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
url: null,
|
|
2090
|
-
loading: true,
|
|
2091
|
-
error: null
|
|
2092
|
-
});
|
|
2093
|
-
record.promise.then((result) => {
|
|
2094
|
-
if (!cancelled) setState(toState$2(result, linkErrorRef.current));
|
|
2095
|
-
}).catch((err) => {
|
|
2096
|
-
if (!cancelled) setState({
|
|
2097
|
-
url: null,
|
|
2098
|
-
loading: false,
|
|
2099
|
-
error: err instanceof Error ? err.message : linkErrorRef.current
|
|
2100
|
-
});
|
|
2101
|
-
});
|
|
2102
|
-
return () => {
|
|
2103
|
-
cancelled = true;
|
|
2104
|
-
};
|
|
2105
|
-
}, [path]);
|
|
2106
|
-
const cached = path ? peekPreviewUrl(path) : void 0;
|
|
2107
|
-
return cached ? toState$2(cached, linkErrorRef.current) : state;
|
|
1951
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1952
|
+
}
|
|
1953
|
+
notify.warning(strings.corsPropagating);
|
|
2108
1954
|
}
|
|
2109
1955
|
//#endregion
|
|
2110
1956
|
//#region src/file-explorer/FilePreview/parse.ts
|
|
@@ -2118,11 +1964,12 @@ function parseDelimited(text, delimiter, maxRows) {
|
|
|
2118
1964
|
for (let i = 0; i < text.length; i++) {
|
|
2119
1965
|
const char = text[i];
|
|
2120
1966
|
if (inQuotes) {
|
|
2121
|
-
if (char === "\"")
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
1967
|
+
if (char === "\"") {
|
|
1968
|
+
if (text[i + 1] === "\"") {
|
|
1969
|
+
field += "\"";
|
|
1970
|
+
i++;
|
|
1971
|
+
} else inQuotes = false;
|
|
1972
|
+
} else field += char;
|
|
2126
1973
|
continue;
|
|
2127
1974
|
}
|
|
2128
1975
|
if (char === "\"") inQuotes = true;
|
|
@@ -2176,12 +2023,12 @@ async function readCappedBytes(response, maxBytes) {
|
|
|
2176
2023
|
const header = response.headers.get("content-length");
|
|
2177
2024
|
const declared = header === null ? NaN : Number(header);
|
|
2178
2025
|
if (!Number.isFinite(declared) || declared > maxBytes) return {
|
|
2179
|
-
bytes: new Uint8Array(0),
|
|
2026
|
+
bytes: /* @__PURE__ */ new Uint8Array(0),
|
|
2180
2027
|
tooLarge: true
|
|
2181
2028
|
};
|
|
2182
2029
|
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
2183
2030
|
return buffer.byteLength > maxBytes ? {
|
|
2184
|
-
bytes: new Uint8Array(0),
|
|
2031
|
+
bytes: /* @__PURE__ */ new Uint8Array(0),
|
|
2185
2032
|
tooLarge: true
|
|
2186
2033
|
} : {
|
|
2187
2034
|
bytes: buffer,
|
|
@@ -2195,7 +2042,7 @@ async function readCappedBytes(response, maxBytes) {
|
|
|
2195
2042
|
if (total > maxBytes) {
|
|
2196
2043
|
await reader.cancel();
|
|
2197
2044
|
return {
|
|
2198
|
-
bytes: new Uint8Array(0),
|
|
2045
|
+
bytes: /* @__PURE__ */ new Uint8Array(0),
|
|
2199
2046
|
tooLarge: true
|
|
2200
2047
|
};
|
|
2201
2048
|
}
|
|
@@ -2557,7 +2404,7 @@ function useObjectText(url, { enabled, maxBytes, maxLines, knownSize, cacheKey,
|
|
|
2557
2404
|
}
|
|
2558
2405
|
//#endregion
|
|
2559
2406
|
//#region src/file-explorer/FilePreview/Viewer.tsx
|
|
2560
|
-
var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-
|
|
2407
|
+
var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-YqQBz1YL.js"));
|
|
2561
2408
|
var PREVIEW_MARKDOWN_CONTROLS = {
|
|
2562
2409
|
table: { fullscreen: false },
|
|
2563
2410
|
mermaid: { fullscreen: false }
|
|
@@ -2873,20 +2720,20 @@ function CsvTable({ node, text }) {
|
|
|
2873
2720
|
children: [/* @__PURE__ */ jsx("th", {
|
|
2874
2721
|
className: "w-11 border-b theme-border px-3.5 py-2 text-right font-semibold theme-muted-text",
|
|
2875
2722
|
children: "#"
|
|
2876
|
-
}),
|
|
2723
|
+
}), positionKeys(columnCount, "column").map((key, i) => /* @__PURE__ */ jsx("th", {
|
|
2877
2724
|
className: "border-b theme-border px-3.5 py-2 text-left font-semibold",
|
|
2878
2725
|
children: header[i] ?? ""
|
|
2879
|
-
},
|
|
2880
|
-
}) }), /* @__PURE__ */ jsx("tbody", { children: shown.map((row, r) => /* @__PURE__ */ jsxs("tr", {
|
|
2726
|
+
}, key))]
|
|
2727
|
+
}) }), /* @__PURE__ */ jsx("tbody", { children: withPositionKeys(shown, "row").map(({ key, item: row }, r) => /* @__PURE__ */ jsxs("tr", {
|
|
2881
2728
|
className: cx(r % 2 === 1 && "bg-(--theme-panel-bg)/50"),
|
|
2882
2729
|
children: [/* @__PURE__ */ jsx("td", {
|
|
2883
2730
|
className: "px-3.5 py-1.5 text-right theme-muted-text",
|
|
2884
2731
|
children: r + 1
|
|
2885
|
-
}),
|
|
2732
|
+
}), positionKeys(columnCount, "cell").map((cellKey, c) => /* @__PURE__ */ jsx("td", {
|
|
2886
2733
|
className: "px-3.5 py-1.5",
|
|
2887
2734
|
children: row[c] ?? ""
|
|
2888
|
-
},
|
|
2889
|
-
},
|
|
2735
|
+
}, cellKey))]
|
|
2736
|
+
}, key)) })]
|
|
2890
2737
|
})
|
|
2891
2738
|
}), /* @__PURE__ */ jsx("div", {
|
|
2892
2739
|
className: "shrink-0 border-t theme-border px-3.5 py-1.5 font-mono text-[11px] theme-muted-text",
|
|
@@ -2926,7 +2773,7 @@ function NotebookBody({ text, onDownload }) {
|
|
|
2926
2773
|
className: "h-full w-full overflow-auto bg-(--theme-app-bg) p-4",
|
|
2927
2774
|
children: /* @__PURE__ */ jsx("div", {
|
|
2928
2775
|
className: "mx-auto flex max-w-4xl flex-col gap-3",
|
|
2929
|
-
children: notebook.cells.map((
|
|
2776
|
+
children: withPositionKeys(notebook.cells, "cell").map(({ key, item: cell }) => {
|
|
2930
2777
|
const source = joinSource(cell.source);
|
|
2931
2778
|
if (cell.cell_type === "markdown") return /* @__PURE__ */ jsx("div", {
|
|
2932
2779
|
className: "rounded-lg border theme-border px-4 py-3",
|
|
@@ -2934,7 +2781,7 @@ function NotebookBody({ text, onDownload }) {
|
|
|
2934
2781
|
controls: PREVIEW_MARKDOWN_CONTROLS,
|
|
2935
2782
|
children: source
|
|
2936
2783
|
})
|
|
2937
|
-
},
|
|
2784
|
+
}, key);
|
|
2938
2785
|
if (cell.cell_type === "code") return /* @__PURE__ */ jsxs("div", {
|
|
2939
2786
|
className: "flex gap-2",
|
|
2940
2787
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -2945,9 +2792,9 @@ function NotebookBody({ text, onDownload }) {
|
|
|
2945
2792
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2946
2793
|
className: "rounded-lg border theme-border px-1 py-1 text-xs",
|
|
2947
2794
|
children: /* @__PURE__ */ jsx(Markdown, { children: toFencedCode(source, "python") })
|
|
2948
|
-
}), (cell.outputs ?? []).map((
|
|
2795
|
+
}), withPositionKeys(cell.outputs ?? [], "output").map(({ key: outputKey, item: output }) => /* @__PURE__ */ jsx(NotebookOutputView, { output }, outputKey))]
|
|
2949
2796
|
})]
|
|
2950
|
-
},
|
|
2797
|
+
}, key);
|
|
2951
2798
|
return null;
|
|
2952
2799
|
})
|
|
2953
2800
|
})
|
|
@@ -3325,13 +3172,14 @@ function FilePreviewInline({ node, storage, getPresignedUrl, onOpenFull, onDownl
|
|
|
3325
3172
|
enabled: active,
|
|
3326
3173
|
linkErrorMessage: t.preview.linkError
|
|
3327
3174
|
});
|
|
3175
|
+
const kind = getPreviewKind(node);
|
|
3328
3176
|
return /* @__PURE__ */ jsxs("div", {
|
|
3329
3177
|
className: "flex h-full min-h-0 flex-col",
|
|
3330
3178
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3331
3179
|
className: "min-h-0 flex-1 overflow-hidden",
|
|
3332
3180
|
children: /* @__PURE__ */ jsx(FilePreviewViewer, {
|
|
3333
3181
|
node,
|
|
3334
|
-
kind
|
|
3182
|
+
kind,
|
|
3335
3183
|
url,
|
|
3336
3184
|
urlLoading: loading,
|
|
3337
3185
|
urlError: error,
|
|
@@ -3768,7 +3616,6 @@ function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll
|
|
|
3768
3616
|
case "End":
|
|
3769
3617
|
go(nodes.length - 1);
|
|
3770
3618
|
return;
|
|
3771
|
-
default: break;
|
|
3772
3619
|
}
|
|
3773
3620
|
if (e.target !== e.currentTarget || from < 0) return;
|
|
3774
3621
|
const node = nodes[from];
|
|
@@ -3859,6 +3706,128 @@ function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll
|
|
|
3859
3706
|
})
|
|
3860
3707
|
});
|
|
3861
3708
|
}
|
|
3709
|
+
//#endregion
|
|
3710
|
+
//#region src/file-explorer/lib/paging.ts
|
|
3711
|
+
/** Both providers already cap an unqualified list at 1,000, so stating it leaves
|
|
3712
|
+
* single-page directories byte-identical. */
|
|
3713
|
+
var LIST_PAGE_SIZE = 1e3;
|
|
3714
|
+
function isListed(listings, path) {
|
|
3715
|
+
return (listings.get(path)?.pagesLoaded ?? 0) > 0;
|
|
3716
|
+
}
|
|
3717
|
+
/** True even while a request is in flight, so the sentinel stays put. */
|
|
3718
|
+
function hasMore(listings, path) {
|
|
3719
|
+
return !!listings.get(path)?.cursor;
|
|
3720
|
+
}
|
|
3721
|
+
/** A failed page needs an explicit retry, so it stops auto-loading here. */
|
|
3722
|
+
function canLoadMore(listings, path) {
|
|
3723
|
+
const listing = listings.get(path);
|
|
3724
|
+
return !!listing?.cursor && !listing.loadingMore && !listing.error;
|
|
3725
|
+
}
|
|
3726
|
+
/** Auto-load stops at the cap; past it the user asks for each page. */
|
|
3727
|
+
function canAutoLoadMore(listings, path) {
|
|
3728
|
+
return canLoadMore(listings, path) && (listings.get(path)?.pagesLoaded ?? 0) < 5;
|
|
3729
|
+
}
|
|
3730
|
+
function isLoadingMore(listings, path) {
|
|
3731
|
+
return listings.get(path)?.loadingMore === true;
|
|
3732
|
+
}
|
|
3733
|
+
function loadMoreError(listings, path) {
|
|
3734
|
+
return listings.get(path)?.error;
|
|
3735
|
+
}
|
|
3736
|
+
function beginLoadingMore(listings, path) {
|
|
3737
|
+
const listing = listings.get(path);
|
|
3738
|
+
if (!listing) return listings;
|
|
3739
|
+
return new Map(listings).set(path, {
|
|
3740
|
+
...listing,
|
|
3741
|
+
loadingMore: true,
|
|
3742
|
+
error: void 0
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
function advanceListing(listings, path, page) {
|
|
3746
|
+
const listing = listings.get(path);
|
|
3747
|
+
return new Map(listings).set(path, {
|
|
3748
|
+
cursor: page.cursor,
|
|
3749
|
+
pagesLoaded: (listing?.pagesLoaded ?? 0) + 1,
|
|
3750
|
+
loadingMore: false,
|
|
3751
|
+
entries: [...listing?.entries ?? [], ...page.entries]
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
/** Subfolders the page no longer names lose their records, or a re-created folder
|
|
3755
|
+
* would show old contents; the rest keep theirs, since a refresh lists them
|
|
3756
|
+
* alongside this one and a parent landing later must not undo a child's page. */
|
|
3757
|
+
function replaceListing(listings, path, page) {
|
|
3758
|
+
const prefix = ensureTrailingSlash(path);
|
|
3759
|
+
const subfolders = new Set(page.entries.filter((entry) => entry.type === "directory").map(getNodeName));
|
|
3760
|
+
const next = new Map(listings);
|
|
3761
|
+
next.delete(path);
|
|
3762
|
+
for (const key of listings.keys()) {
|
|
3763
|
+
if (!key.startsWith(prefix)) continue;
|
|
3764
|
+
if (!subfolders.has(key.slice(prefix.length).split("/")[0] ?? "")) next.delete(key);
|
|
3765
|
+
}
|
|
3766
|
+
return advanceListing(next, path, page);
|
|
3767
|
+
}
|
|
3768
|
+
/** `pagesLoaded` stays put, so a failed first page still reads as unlisted. */
|
|
3769
|
+
function recordListingError(listings, path, error) {
|
|
3770
|
+
const listing = listings.get(path);
|
|
3771
|
+
return new Map(listings).set(path, {
|
|
3772
|
+
cursor: listing?.cursor,
|
|
3773
|
+
pagesLoaded: listing?.pagesLoaded ?? 0,
|
|
3774
|
+
loadingMore: false,
|
|
3775
|
+
error,
|
|
3776
|
+
entries: listing?.entries ?? []
|
|
3777
|
+
});
|
|
3778
|
+
}
|
|
3779
|
+
/** Keeps the cursor and pages, so a retry resumes paging instead of starting over. */
|
|
3780
|
+
function clearListingError(listings, path) {
|
|
3781
|
+
const listing = listings.get(path);
|
|
3782
|
+
if (!listing) return listings;
|
|
3783
|
+
return new Map(listings).set(path, {
|
|
3784
|
+
...listing,
|
|
3785
|
+
error: void 0
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3788
|
+
function clearListingsUnder(listings, path) {
|
|
3789
|
+
const prefix = ensureTrailingSlash(path);
|
|
3790
|
+
const next = new Map(listings);
|
|
3791
|
+
for (const key of listings.keys()) if (key === path || key.startsWith(prefix)) next.delete(key);
|
|
3792
|
+
return next;
|
|
3793
|
+
}
|
|
3794
|
+
/** Tree paths whose children should keep arrival order, since sorting them would
|
|
3795
|
+
* reshuffle rows under the user mid-scroll. */
|
|
3796
|
+
function incompletePaths(listings) {
|
|
3797
|
+
const paths = /* @__PURE__ */ new Set();
|
|
3798
|
+
for (const [path, listing] of listings) if (listing.cursor) paths.add(path);
|
|
3799
|
+
return paths;
|
|
3800
|
+
}
|
|
3801
|
+
/** What a deep link needs next. Pure so the walk is table-testable. */
|
|
3802
|
+
function nextHydrationStep({ targetResolved, ancestors, isListableDir, listings, inFlight, pagesPulled }) {
|
|
3803
|
+
for (const path of ancestors) {
|
|
3804
|
+
if (!isListableDir(path)) continue;
|
|
3805
|
+
if (inFlight.has(path)) return { kind: "done" };
|
|
3806
|
+
if (!listings.has(path)) return {
|
|
3807
|
+
kind: "fetch",
|
|
3808
|
+
path
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
if (targetResolved) return { kind: "done" };
|
|
3812
|
+
const deepest = [...ancestors].reverse().find(isListableDir);
|
|
3813
|
+
if (!deepest) return { kind: "done" };
|
|
3814
|
+
if (!hasMore(listings, deepest)) return {
|
|
3815
|
+
kind: "stuck",
|
|
3816
|
+
path: deepest
|
|
3817
|
+
};
|
|
3818
|
+
if (loadMoreError(listings, deepest)) return {
|
|
3819
|
+
kind: "stuck",
|
|
3820
|
+
path: deepest
|
|
3821
|
+
};
|
|
3822
|
+
if (pagesPulled >= 20) return {
|
|
3823
|
+
kind: "stuck",
|
|
3824
|
+
path: deepest
|
|
3825
|
+
};
|
|
3826
|
+
return isLoadingMore(listings, deepest) ? { kind: "done" } : {
|
|
3827
|
+
kind: "more",
|
|
3828
|
+
path: deepest
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3862
3831
|
/** `stepRow` skips these, so neither kind is ever an arrow-key stop. */
|
|
3863
3832
|
function statusRow(kind, node, depth, hiddenFiles = 0) {
|
|
3864
3833
|
return {
|
|
@@ -4100,11 +4069,11 @@ function indentStyle(depth, isDirectory) {
|
|
|
4100
4069
|
}
|
|
4101
4070
|
function IndentGuides({ depth }) {
|
|
4102
4071
|
if (depth === 0) return null;
|
|
4103
|
-
return /* @__PURE__ */ jsx(Fragment$1, { children:
|
|
4072
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: positionKeys(depth, "indent").map((key, level) => /* @__PURE__ */ jsx("span", {
|
|
4104
4073
|
"aria-hidden": "true",
|
|
4105
4074
|
className: "pointer-events-none absolute inset-y-0 w-px bg-(--theme-border)/60",
|
|
4106
4075
|
style: { left: GUIDE_OFFSET + level * INDENT_PER_LEVEL }
|
|
4107
|
-
},
|
|
4076
|
+
}, key)) });
|
|
4108
4077
|
}
|
|
4109
4078
|
function RowIcons({ isDirectory, isExpanded, customIcon }) {
|
|
4110
4079
|
if (!isDirectory) return /* @__PURE__ */ jsx("div", {
|
|
@@ -4208,7 +4177,7 @@ function useSpringLoadedDrop({ node, dropPath, isDirectory, isExpanded, onToggle
|
|
|
4208
4177
|
};
|
|
4209
4178
|
}
|
|
4210
4179
|
/** Keys a row handles itself; the arrows that move between rows delegate upward,
|
|
4211
|
-
* where the flat array is. The set is deliberately narrow
|
|
4180
|
+
* where the flat array is. The set is deliberately narrow:
|
|
4212
4181
|
* no Home/End, no typeahead, and ArrowLeft/Right only toggle a directory. */
|
|
4213
4182
|
function dispatchRowKey(e, { node, isDirectory, isExpanded, onSelect, onToggleExpand, onDelegate }) {
|
|
4214
4183
|
if (e.key === "Enter") {
|
|
@@ -4363,7 +4332,7 @@ function TreeFrame({ treeOnly, sidebarWidth, onWidthChange, children }) {
|
|
|
4363
4332
|
children
|
|
4364
4333
|
});
|
|
4365
4334
|
}
|
|
4366
|
-
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange }) {
|
|
4335
|
+
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand }) {
|
|
4367
4336
|
const copySubmenu = useCopySubmenu();
|
|
4368
4337
|
const t = useStrings().fileExplorer;
|
|
4369
4338
|
const notify = useNotify();
|
|
@@ -4399,7 +4368,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4399
4368
|
return `${selectedPath}/`;
|
|
4400
4369
|
}, [selectedPath, treeMap]);
|
|
4401
4370
|
const setSelectedPath = onPathChange;
|
|
4402
|
-
const [expandedPaths, setExpandedPaths] = useState(() => new Set(["", ...initialExpandedPaths ?? []]));
|
|
4371
|
+
const [expandedPaths, setExpandedPaths] = useState(() => /* @__PURE__ */ new Set(["", ...initialExpandedPaths ?? []]));
|
|
4403
4372
|
const onExpandedPathsChangeRef = useRef(onExpandedPathsChange);
|
|
4404
4373
|
onExpandedPathsChangeRef.current = onExpandedPathsChange;
|
|
4405
4374
|
useEffect(() => {
|
|
@@ -4426,7 +4395,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4426
4395
|
const selectedNodeCanUpload = selectedStorage?.canUpload !== false;
|
|
4427
4396
|
const selectedNodeCanShare = selectedStorage?.canShare !== false;
|
|
4428
4397
|
const selectedNodeCanManageAccess = selectedStorage?.canManageAccess !== false;
|
|
4429
|
-
const selectedNodeSupportsPwUri = selectedStorage?.supportsPwUri !== false;
|
|
4430
4398
|
const [accessDrawerOpen, setAccessDrawerOpen] = useState(false);
|
|
4431
4399
|
const [connectionIssuesMessage, updateConnectionIssuesMessage] = useState(/* @__PURE__ */ new Map());
|
|
4432
4400
|
const [hasCorsIssue, setHasCorsIssue] = useState(/* @__PURE__ */ new Map());
|
|
@@ -4444,7 +4412,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4444
4412
|
useEffect(() => clearPreviewCache, []);
|
|
4445
4413
|
const { openMenu, contextMenu } = useRowMenu();
|
|
4446
4414
|
const [sidebarWidth, setSidebarWidth] = useLocalStorage(SIDEBAR_WIDTH_STORAGE_KEY, 300);
|
|
4447
|
-
const MAX_UPLOAD_SIZE =
|
|
4415
|
+
const MAX_UPLOAD_SIZE = 85899345920;
|
|
4448
4416
|
const isUploadSizeExceeded = (size) => {
|
|
4449
4417
|
return size > MAX_UPLOAD_SIZE;
|
|
4450
4418
|
};
|
|
@@ -4456,7 +4424,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4456
4424
|
});
|
|
4457
4425
|
}, []);
|
|
4458
4426
|
const collapseAll = useCallback(() => {
|
|
4459
|
-
setExpandedPaths(new Set([""]));
|
|
4427
|
+
setExpandedPaths(/* @__PURE__ */ new Set([""]));
|
|
4460
4428
|
}, []);
|
|
4461
4429
|
const getStorageProviderAndClient = async (storageId) => {
|
|
4462
4430
|
const storage = storages.find((s) => s.id === storageId);
|
|
@@ -4499,7 +4467,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4499
4467
|
const factory = await getStorageProviderAndClient(node.storageId);
|
|
4500
4468
|
if (!factory) throw new Error("Failed to connect to storage provider.");
|
|
4501
4469
|
const { storage, provider, client } = factory;
|
|
4502
|
-
if (!storage.bucketName && storage.type !== "azure-azfiles") storage.bucketName = client.getStorageName();
|
|
4503
4470
|
if (provider?.listDirectoryInput && provider?.convertDataToStorageObjects) {
|
|
4504
4471
|
const dir_path = node.root ? "" : node.relativePath ?? "";
|
|
4505
4472
|
const input = provider.listDirectoryInput(storage, dir_path, cursor ? { cursor } : {});
|
|
@@ -4737,7 +4704,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4737
4704
|
await navigator.clipboard.writeText(presignedUrl);
|
|
4738
4705
|
notify.info(t.messages.fileLinkCopied);
|
|
4739
4706
|
};
|
|
4740
|
-
const shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4741
4707
|
const triggerBrowserDownload = (url, filename) => {
|
|
4742
4708
|
const a = document.createElement("a");
|
|
4743
4709
|
a.href = url;
|
|
@@ -4771,16 +4737,12 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4771
4737
|
if (!expandedPaths.has(node.path)) handleToggleExpand(node.path);
|
|
4772
4738
|
};
|
|
4773
4739
|
const getNodeUris = (node) => {
|
|
4774
|
-
const key = getObjectKeyFromNode(node);
|
|
4775
4740
|
const storage = storages.find((s) => s.id === node.storageId);
|
|
4776
|
-
return
|
|
4777
|
-
|
|
4778
|
-
cspUri: storage?.csp && storage?.bucketName ? getCspUri(storage.csp, storage.bucketName, key) : null
|
|
4779
|
-
};
|
|
4741
|
+
if (!storage || !objectUris) return [];
|
|
4742
|
+
return objectUris(storage, getObjectKeyFromNode(node));
|
|
4780
4743
|
};
|
|
4781
4744
|
const copyNodeUri = async (node) => {
|
|
4782
|
-
const
|
|
4783
|
-
const uri = pwUri ?? cspUri;
|
|
4745
|
+
const uri = getNodeUris(node)[0];
|
|
4784
4746
|
if (!uri) {
|
|
4785
4747
|
notify.error(t.messages.noUri);
|
|
4786
4748
|
return;
|
|
@@ -4827,7 +4789,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4827
4789
|
const nodeCanWrite = storage?.canWrite === true;
|
|
4828
4790
|
const nodeCanShare = storage?.canShare !== false;
|
|
4829
4791
|
const isFile = node.type === "file";
|
|
4830
|
-
const
|
|
4792
|
+
const uris = getNodeUris(node);
|
|
4831
4793
|
const actions = [];
|
|
4832
4794
|
if (isFile && canPreview(node)) actions.push({
|
|
4833
4795
|
key: "preview",
|
|
@@ -4863,7 +4825,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4863
4825
|
icon: isFile ? /* @__PURE__ */ jsx(FileTextIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {}),
|
|
4864
4826
|
onSelect: () => handleSelectTreeItem(node.path, node)
|
|
4865
4827
|
}];
|
|
4866
|
-
const uriExtras =
|
|
4828
|
+
const uriExtras = uris.map((value) => ({
|
|
4867
4829
|
label: `${getUriScheme(value).toUpperCase()} URI`,
|
|
4868
4830
|
value,
|
|
4869
4831
|
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
@@ -4920,13 +4882,16 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4920
4882
|
if (checkedItems.size > 0) filesToDownload = Array.from(checkedItems).map((path) => treeMap[path]).filter((node) => !!node && node.type === "file");
|
|
4921
4883
|
else if (selectedNode && selectedNode.type === "file") filesToDownload = [selectedNode];
|
|
4922
4884
|
if (filesToDownload.length === 0) return "";
|
|
4923
|
-
|
|
4885
|
+
if (!downloadCommand) return "";
|
|
4886
|
+
const objects = filesToDownload.flatMap((node) => {
|
|
4924
4887
|
const storage = storages.find((s) => s.id === node.storageId);
|
|
4925
|
-
|
|
4926
|
-
|
|
4888
|
+
return storage ? [{
|
|
4889
|
+
storage,
|
|
4890
|
+
key: getObjectKeyFromNode(node),
|
|
4891
|
+
name: node.name
|
|
4892
|
+
}] : [];
|
|
4927
4893
|
});
|
|
4928
|
-
|
|
4929
|
-
else return commands.join(" & \\\n") + " & \\\nwait";
|
|
4894
|
+
return downloadCommand.build(objects);
|
|
4930
4895
|
};
|
|
4931
4896
|
const onDeleteClick = () => {
|
|
4932
4897
|
if (!selectedNodeCanWrite) {
|
|
@@ -4965,7 +4930,8 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4965
4930
|
notify.error(t.messages.noItemsForDeletion);
|
|
4966
4931
|
return;
|
|
4967
4932
|
}
|
|
4968
|
-
const
|
|
4933
|
+
const parentPath = getParentPath(pendingDelete[0].path);
|
|
4934
|
+
const parentNode = treeMap[parentPath];
|
|
4969
4935
|
const factory = await getStorageProviderAndClient(pendingDelete[0].storageId);
|
|
4970
4936
|
if (!factory) return;
|
|
4971
4937
|
const { storage, provider, client } = factory;
|
|
@@ -5320,20 +5286,20 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5320
5286
|
type: "button",
|
|
5321
5287
|
className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
|
|
5322
5288
|
onClick: handleDownload,
|
|
5323
|
-
children:
|
|
5324
|
-
}) }),
|
|
5289
|
+
children: t.chrome.downloadFromBrowser
|
|
5290
|
+
}) }), downloadCommand && generateDownloadCommand() && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(MenuSeparator, { className: "my-0.5 h-px theme-border" }), /* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
5325
5291
|
className: "text-sm w-full text-center pointer-events-none px-2 py-1.5",
|
|
5326
|
-
children:
|
|
5292
|
+
children: downloadCommand.label
|
|
5327
5293
|
}), (() => {
|
|
5328
5294
|
const downloadCommand = generateDownloadCommand();
|
|
5329
5295
|
return /* @__PURE__ */ jsx(CopyCodeBlock, {
|
|
5330
5296
|
textToCopy: downloadCommand,
|
|
5331
5297
|
children: /* @__PURE__ */ jsx("div", {
|
|
5332
5298
|
className: "overflow-x-auto text-xs",
|
|
5333
|
-
children: downloadCommand.split("\n").map((
|
|
5299
|
+
children: keyedByContent(downloadCommand.split("\n"), (l) => l).map(({ key, item: line }) => /* @__PURE__ */ jsx("div", {
|
|
5334
5300
|
className: "whitespace-nowrap",
|
|
5335
5301
|
children: line
|
|
5336
|
-
},
|
|
5302
|
+
}, key))
|
|
5337
5303
|
})
|
|
5338
5304
|
});
|
|
5339
5305
|
})()] }) })] })]
|
|
@@ -5401,7 +5367,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5401
5367
|
}))]
|
|
5402
5368
|
}),
|
|
5403
5369
|
selectedNode.type === "file" && (() => {
|
|
5404
|
-
const
|
|
5370
|
+
const uris = getNodeUris(selectedNode);
|
|
5405
5371
|
const fileIcon = getNodeIcon(selectedNode, slots.storageIconUrl, t.chrome.storageIconAlt) ?? /* @__PURE__ */ jsx(FileIcon, { className: "h-5 w-5 theme-muted-text" });
|
|
5406
5372
|
const modifiedDt = selectedNode.modified ? DateTime.fromISO(selectedNode.modified) : null;
|
|
5407
5373
|
const createdDt = selectedNode.created ? DateTime.fromISO(selectedNode.created) : null;
|
|
@@ -5465,9 +5431,9 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5465
5431
|
})]
|
|
5466
5432
|
})]
|
|
5467
5433
|
}),
|
|
5468
|
-
|
|
5434
|
+
uris.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
5469
5435
|
className: "px-6 pt-5 space-y-2",
|
|
5470
|
-
children:
|
|
5436
|
+
children: uris.map((uri) => /* @__PURE__ */ jsx(UriCard, { value: uri }, uri))
|
|
5471
5437
|
}),
|
|
5472
5438
|
selectedNodeCanShare && /* @__PURE__ */ jsxs("div", {
|
|
5473
5439
|
className: "flex items-center gap-2 px-6 pt-4",
|