@parallelworks/ui 0.17.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-UAtnpmOV.js → CodePreviewEditor-YqQBz1YL.js} +2 -2
- package/dist/chunks/ConfirmModal-aOR4O1QE.js +79 -0
- package/dist/chunks/{lib-CUTRDpa6.js → FieldWrapper-Cszwg-4B.js} +52 -55
- package/dist/chunks/{CreateModal-BbV5cTj7.js → Markdown-CM-FenNE.js} +25 -97
- package/dist/chunks/Monaco-DJ8MW-Dq.js +110 -0
- package/dist/chunks/{PageHeader-MxuOs9cH.js → PageHeader-0kG_iypP.js} +170 -105
- package/dist/chunks/{Provider-DHmZ3v9i.js → Provider-CDYurraE.js} +60 -24
- package/dist/chunks/{SwitchToggle-BRQoEwLZ.js → SwitchToggle-DShNdcao.js} +143 -140
- package/dist/chunks/{Table-C6JdFFiJ.js → Table-DU2MW8xz.js} +6 -4
- package/dist/chunks/{Tooltip-n9w7Tkt1.js → Tooltip-K5HJr4_p.js} +32 -5
- package/dist/chunks/duration-BOv8Phl8.js +17 -0
- package/dist/chunks/{editor-mNznodJN.js → editor-Dk1rbV75.js} +33 -19
- package/dist/chunks/focus-D_iZJTW1.js +7 -0
- package/dist/chunks/{list-7k0eaI5N.js → list-CJm875m7.js} +497 -379
- 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/AddMenu.d.ts +1 -0
- package/dist/components/Avatar.d.ts +13 -0
- package/dist/components/Breadcrumbs.d.ts +1 -1
- package/dist/components/Dropdown.d.ts +3 -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 +18 -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 +632 -665
- 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 -1
- package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
- package/dist/form/Form.d.ts +7 -22
- package/dist/form/index.d.ts +11 -12
- package/dist/form/index.js +1333 -1454
- package/dist/form/lib.d.ts +6 -1
- package/dist/form/types/fieldComponentTypes.d.ts +4 -2
- package/dist/form/types/fieldTypes.d.ts +6 -20
- package/dist/form/useParsedOpts.d.ts +2 -2
- 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 +220 -185
- package/dist/{workflow → graph}/types.d.ts +3 -3
- package/dist/icons.d.ts +20 -21
- package/dist/icons.js +3 -3
- package/dist/index.d.ts +38 -39
- package/dist/index.js +355 -354
- package/dist/list/ListTable.d.ts +1 -1
- package/dist/list/ListViewControls.d.ts +2 -2
- package/dist/list/RowContextMenu.d.ts +9 -9
- package/dist/list/UserHoverCard.d.ts +45 -0
- package/dist/list/index.d.ts +9 -8
- package/dist/list/index.js +3 -3
- 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/Geist-Variable.woff2 +0 -0
- package/dist/styles/GeistMono-Variable.woff2 +0 -0
- package/dist/styles/base.css +57 -0
- package/dist/styles/fonts.css +15 -0
- package/dist/styles/theme.css +9 -41
- package/dist/styles.css +1 -1
- package/dist/theme/index.js +2 -2
- package/package.json +45 -20
- package/LICENSE +0 -21
- package/dist/chunks/Monaco-DVyIpyv4.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/{BareModal-BqroLbZ5.js → BareModal-BtRiQezg.js} +1 -1
- 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, s as useProvisionCorsRules, u as useStrings } from "../chunks/Provider-CDYurraE.js";
|
|
1
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";
|
|
2
|
-
import { c as
|
|
3
|
-
import {
|
|
4
|
-
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";
|
|
5
7
|
import { t as StatusBadge } from "../chunks/StatusBadge-C1Blx_cW.js";
|
|
6
8
|
import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
13
|
import { Suspense, createContext, lazy, memo, use, useCallback, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
|
|
13
14
|
import { useLocalStorage } from "usehooks-ts";
|
|
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);
|
|
@@ -451,7 +429,7 @@ function FileProgressBar({ fileProgress, fileName, progressBarColorScheme, onCan
|
|
|
451
429
|
children: [/* @__PURE__ */ jsx("div", {
|
|
452
430
|
className: cx("h-full transition-all duration-300 relative flex items-center justify-center", barColor, isHovered && "opacity-80"),
|
|
453
431
|
style: { width: `${percentage}%` }
|
|
454
|
-
}), isHovered && !isCompleted && totalBytes > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
432
|
+
}), isHovered && !isCompleted && totalBytes > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
455
433
|
className: "absolute inset-0 flex items-center justify-center text-xs whitespace-nowrap p-0.5 z-10",
|
|
456
434
|
style: {
|
|
457
435
|
WebkitClipPath: `inset(0 ${100 - percentage}% 0 0)`,
|
|
@@ -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",
|
|
@@ -625,7 +593,7 @@ function UploadSessionToast({ session, queueLength, onCancel, onCancelFile }) {
|
|
|
625
593
|
})
|
|
626
594
|
]
|
|
627
595
|
})]
|
|
628
|
-
}), isToastExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
596
|
+
}), isToastExpanded && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
629
597
|
/* @__PURE__ */ jsxs("div", {
|
|
630
598
|
className: "flex items-center justify-between",
|
|
631
599
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -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" });
|
|
@@ -1493,7 +1787,7 @@ function PropagationStage({ status, label }) {
|
|
|
1493
1787
|
"aria-hidden": "true",
|
|
1494
1788
|
children: /* @__PURE__ */ jsx("polyline", { points: "3.5 8.5 6.5 11.5 12.5 5" })
|
|
1495
1789
|
}),
|
|
1496
|
-
status === "active" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400/60" }), /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-blue-500" })] }),
|
|
1790
|
+
status === "active" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400/60" }), /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-blue-500" })] }),
|
|
1497
1791
|
status === "pending" && /* @__PURE__ */ jsx("span", { className: "inline-flex h-1.5 w-1.5 rounded-full theme-muted-text opacity-60" })
|
|
1498
1792
|
]
|
|
1499
1793
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -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
|
})
|
|
@@ -2998,7 +2845,7 @@ function PreviewFetchState({ loading, tooLarge, error, corsError, storage, onCor
|
|
|
2998
2845
|
});
|
|
2999
2846
|
if (tooLarge) return /* @__PURE__ */ jsx(TooLargeInline, { onDownload });
|
|
3000
2847
|
if (error) return /* @__PURE__ */ jsx(PreviewError, { onDownload });
|
|
3001
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
2848
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
3002
2849
|
}
|
|
3003
2850
|
function NotPreviewable({ icon, title, message, onDownload }) {
|
|
3004
2851
|
const t = useStrings().fileExplorer;
|
|
@@ -3247,7 +3094,7 @@ function FilePreview({ open, node, siblings, storage, getPresignedUrl, onNavigat
|
|
|
3247
3094
|
onClick: (e) => {
|
|
3248
3095
|
if (e.target === e.currentTarget) onClose();
|
|
3249
3096
|
},
|
|
3250
|
-
children: [isOverlay && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EdgeButton, {
|
|
3097
|
+
children: [isOverlay && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(EdgeButton, {
|
|
3251
3098
|
side: "left",
|
|
3252
3099
|
label: t.preview.previousFile,
|
|
3253
3100
|
disabled: !hasPrev,
|
|
@@ -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,
|
|
@@ -3592,7 +3440,7 @@ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeI
|
|
|
3592
3440
|
const storageIconAlt = useStrings().fileExplorer.chrome.storageIconAlt;
|
|
3593
3441
|
useNestedScrollerWarning(scrollEl, nodes.length);
|
|
3594
3442
|
useRowPitchAudit(scrollEl, nodes, items);
|
|
3595
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3443
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3596
3444
|
padTop > 0 && /* @__PURE__ */ jsx(Spacer, { height: padTop }, "pad-top"),
|
|
3597
3445
|
items.map((item) => {
|
|
3598
3446
|
const node = nodes[item.index];
|
|
@@ -3625,7 +3473,7 @@ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeI
|
|
|
3625
3473
|
] });
|
|
3626
3474
|
}
|
|
3627
3475
|
function Spacer({ height }) {
|
|
3628
|
-
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("tr", {
|
|
3476
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx("tr", {
|
|
3629
3477
|
"aria-hidden": "true",
|
|
3630
3478
|
className: "border-0",
|
|
3631
3479
|
children: /* @__PURE__ */ jsx("td", {
|
|
@@ -3658,7 +3506,7 @@ function useRowPitchAudit(scrollEl, nodes, items) {
|
|
|
3658
3506
|
* would land in `<tbody>`, since Children.toArray only flattens arrays. */
|
|
3659
3507
|
function FileTableHeaders({ allSelected, someSelected, onSelectAll }) {
|
|
3660
3508
|
const t = useStrings().fileExplorer;
|
|
3661
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3509
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3662
3510
|
/* @__PURE__ */ jsx(Table.Header, {
|
|
3663
3511
|
caps: false,
|
|
3664
3512
|
className: "py-2",
|
|
@@ -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, { 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", {
|
|
@@ -4112,7 +4081,7 @@ function RowIcons({ isDirectory, isExpanded, customIcon }) {
|
|
|
4112
4081
|
children: customIcon
|
|
4113
4082
|
});
|
|
4114
4083
|
const folder = isExpanded ? /* @__PURE__ */ jsx(FolderOpenIcon, { className: "h-4 w-4 mr-1 text-sky-500" }) : /* @__PURE__ */ jsx(FolderIcon, { className: "h-4 w-4 mr-1 text-sky-500/80" });
|
|
4115
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx("h-3.5 w-3.5 shrink-0 theme-muted-text transition-transform duration-150", isExpanded && "rotate-90") }), customIcon ?? folder] });
|
|
4084
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx("h-3.5 w-3.5 shrink-0 theme-muted-text transition-transform duration-150", isExpanded && "rotate-90") }), customIcon ?? folder] });
|
|
4116
4085
|
}
|
|
4117
4086
|
/** Sized by the slot, not by its contents: react-loading-skeleton wraps each bar
|
|
4118
4087
|
* in an inline span, so a bar occupies a line box taller than its `height` and
|
|
@@ -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,8 @@ 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 }) {
|
|
4336
|
+
const copySubmenu = useCopySubmenu();
|
|
4367
4337
|
const t = useStrings().fileExplorer;
|
|
4368
4338
|
const notify = useNotify();
|
|
4369
4339
|
const slots = useSlots();
|
|
@@ -4398,7 +4368,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4398
4368
|
return `${selectedPath}/`;
|
|
4399
4369
|
}, [selectedPath, treeMap]);
|
|
4400
4370
|
const setSelectedPath = onPathChange;
|
|
4401
|
-
const [expandedPaths, setExpandedPaths] = useState(() => new Set(["", ...initialExpandedPaths ?? []]));
|
|
4371
|
+
const [expandedPaths, setExpandedPaths] = useState(() => /* @__PURE__ */ new Set(["", ...initialExpandedPaths ?? []]));
|
|
4402
4372
|
const onExpandedPathsChangeRef = useRef(onExpandedPathsChange);
|
|
4403
4373
|
onExpandedPathsChangeRef.current = onExpandedPathsChange;
|
|
4404
4374
|
useEffect(() => {
|
|
@@ -4425,7 +4395,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4425
4395
|
const selectedNodeCanUpload = selectedStorage?.canUpload !== false;
|
|
4426
4396
|
const selectedNodeCanShare = selectedStorage?.canShare !== false;
|
|
4427
4397
|
const selectedNodeCanManageAccess = selectedStorage?.canManageAccess !== false;
|
|
4428
|
-
const selectedNodeSupportsPwUri = selectedStorage?.supportsPwUri !== false;
|
|
4429
4398
|
const [accessDrawerOpen, setAccessDrawerOpen] = useState(false);
|
|
4430
4399
|
const [connectionIssuesMessage, updateConnectionIssuesMessage] = useState(/* @__PURE__ */ new Map());
|
|
4431
4400
|
const [hasCorsIssue, setHasCorsIssue] = useState(/* @__PURE__ */ new Map());
|
|
@@ -4443,7 +4412,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4443
4412
|
useEffect(() => clearPreviewCache, []);
|
|
4444
4413
|
const { openMenu, contextMenu } = useRowMenu();
|
|
4445
4414
|
const [sidebarWidth, setSidebarWidth] = useLocalStorage(SIDEBAR_WIDTH_STORAGE_KEY, 300);
|
|
4446
|
-
const MAX_UPLOAD_SIZE =
|
|
4415
|
+
const MAX_UPLOAD_SIZE = 85899345920;
|
|
4447
4416
|
const isUploadSizeExceeded = (size) => {
|
|
4448
4417
|
return size > MAX_UPLOAD_SIZE;
|
|
4449
4418
|
};
|
|
@@ -4455,7 +4424,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4455
4424
|
});
|
|
4456
4425
|
}, []);
|
|
4457
4426
|
const collapseAll = useCallback(() => {
|
|
4458
|
-
setExpandedPaths(new Set([""]));
|
|
4427
|
+
setExpandedPaths(/* @__PURE__ */ new Set([""]));
|
|
4459
4428
|
}, []);
|
|
4460
4429
|
const getStorageProviderAndClient = async (storageId) => {
|
|
4461
4430
|
const storage = storages.find((s) => s.id === storageId);
|
|
@@ -4498,7 +4467,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4498
4467
|
const factory = await getStorageProviderAndClient(node.storageId);
|
|
4499
4468
|
if (!factory) throw new Error("Failed to connect to storage provider.");
|
|
4500
4469
|
const { storage, provider, client } = factory;
|
|
4501
|
-
if (!storage.bucketName && storage.type !== "azure-azfiles") storage.bucketName = client.getStorageName();
|
|
4502
4470
|
if (provider?.listDirectoryInput && provider?.convertDataToStorageObjects) {
|
|
4503
4471
|
const dir_path = node.root ? "" : node.relativePath ?? "";
|
|
4504
4472
|
const input = provider.listDirectoryInput(storage, dir_path, cursor ? { cursor } : {});
|
|
@@ -4736,7 +4704,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4736
4704
|
await navigator.clipboard.writeText(presignedUrl);
|
|
4737
4705
|
notify.info(t.messages.fileLinkCopied);
|
|
4738
4706
|
};
|
|
4739
|
-
const shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4740
4707
|
const triggerBrowserDownload = (url, filename) => {
|
|
4741
4708
|
const a = document.createElement("a");
|
|
4742
4709
|
a.href = url;
|
|
@@ -4770,16 +4737,12 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4770
4737
|
if (!expandedPaths.has(node.path)) handleToggleExpand(node.path);
|
|
4771
4738
|
};
|
|
4772
4739
|
const getNodeUris = (node) => {
|
|
4773
|
-
const key = getObjectKeyFromNode(node);
|
|
4774
4740
|
const storage = storages.find((s) => s.id === node.storageId);
|
|
4775
|
-
return
|
|
4776
|
-
|
|
4777
|
-
cspUri: storage?.csp && storage?.bucketName ? getCspUri(storage.csp, storage.bucketName, key) : null
|
|
4778
|
-
};
|
|
4741
|
+
if (!storage || !objectUris) return [];
|
|
4742
|
+
return objectUris(storage, getObjectKeyFromNode(node));
|
|
4779
4743
|
};
|
|
4780
4744
|
const copyNodeUri = async (node) => {
|
|
4781
|
-
const
|
|
4782
|
-
const uri = pwUri ?? cspUri;
|
|
4745
|
+
const uri = getNodeUris(node)[0];
|
|
4783
4746
|
if (!uri) {
|
|
4784
4747
|
notify.error(t.messages.noUri);
|
|
4785
4748
|
return;
|
|
@@ -4826,7 +4789,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4826
4789
|
const nodeCanWrite = storage?.canWrite === true;
|
|
4827
4790
|
const nodeCanShare = storage?.canShare !== false;
|
|
4828
4791
|
const isFile = node.type === "file";
|
|
4829
|
-
const
|
|
4792
|
+
const uris = getNodeUris(node);
|
|
4830
4793
|
const actions = [];
|
|
4831
4794
|
if (isFile && canPreview(node)) actions.push({
|
|
4832
4795
|
key: "preview",
|
|
@@ -4862,7 +4825,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4862
4825
|
icon: isFile ? /* @__PURE__ */ jsx(FileTextIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {}),
|
|
4863
4826
|
onSelect: () => handleSelectTreeItem(node.path, node)
|
|
4864
4827
|
}];
|
|
4865
|
-
const uriExtras =
|
|
4828
|
+
const uriExtras = uris.map((value) => ({
|
|
4866
4829
|
label: `${getUriScheme(value).toUpperCase()} URI`,
|
|
4867
4830
|
value,
|
|
4868
4831
|
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
@@ -4919,13 +4882,16 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4919
4882
|
if (checkedItems.size > 0) filesToDownload = Array.from(checkedItems).map((path) => treeMap[path]).filter((node) => !!node && node.type === "file");
|
|
4920
4883
|
else if (selectedNode && selectedNode.type === "file") filesToDownload = [selectedNode];
|
|
4921
4884
|
if (filesToDownload.length === 0) return "";
|
|
4922
|
-
|
|
4885
|
+
if (!downloadCommand) return "";
|
|
4886
|
+
const objects = filesToDownload.flatMap((node) => {
|
|
4923
4887
|
const storage = storages.find((s) => s.id === node.storageId);
|
|
4924
|
-
|
|
4925
|
-
|
|
4888
|
+
return storage ? [{
|
|
4889
|
+
storage,
|
|
4890
|
+
key: getObjectKeyFromNode(node),
|
|
4891
|
+
name: node.name
|
|
4892
|
+
}] : [];
|
|
4926
4893
|
});
|
|
4927
|
-
|
|
4928
|
-
else return commands.join(" & \\\n") + " & \\\nwait";
|
|
4894
|
+
return downloadCommand.build(objects);
|
|
4929
4895
|
};
|
|
4930
4896
|
const onDeleteClick = () => {
|
|
4931
4897
|
if (!selectedNodeCanWrite) {
|
|
@@ -4964,7 +4930,8 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4964
4930
|
notify.error(t.messages.noItemsForDeletion);
|
|
4965
4931
|
return;
|
|
4966
4932
|
}
|
|
4967
|
-
const
|
|
4933
|
+
const parentPath = getParentPath(pendingDelete[0].path);
|
|
4934
|
+
const parentNode = treeMap[parentPath];
|
|
4968
4935
|
const factory = await getStorageProviderAndClient(pendingDelete[0].storageId);
|
|
4969
4936
|
if (!factory) return;
|
|
4970
4937
|
const { storage, provider, client } = factory;
|
|
@@ -5228,7 +5195,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5228
5195
|
selectedStorage: null,
|
|
5229
5196
|
onRefresh: handleRefresh
|
|
5230
5197
|
})]
|
|
5231
|
-
}) : !selectedNode ? /* @__PURE__ */ jsx(NoFileSelectedPreview, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5198
|
+
}) : !selectedNode ? /* @__PURE__ */ jsx(NoFileSelectedPreview, {}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5232
5199
|
/* @__PURE__ */ jsxs("div", {
|
|
5233
5200
|
className: "flex items-center justify-between gap-3 px-4 h-12 shrink-0 border-b theme-border",
|
|
5234
5201
|
children: [renderBreadcrumb(), /* @__PURE__ */ jsxs("div", {
|
|
@@ -5319,20 +5286,20 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5319
5286
|
type: "button",
|
|
5320
5287
|
className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
|
|
5321
5288
|
onClick: handleDownload,
|
|
5322
|
-
children:
|
|
5323
|
-
}) }),
|
|
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", {
|
|
5324
5291
|
className: "text-sm w-full text-center pointer-events-none px-2 py-1.5",
|
|
5325
|
-
children:
|
|
5292
|
+
children: downloadCommand.label
|
|
5326
5293
|
}), (() => {
|
|
5327
5294
|
const downloadCommand = generateDownloadCommand();
|
|
5328
5295
|
return /* @__PURE__ */ jsx(CopyCodeBlock, {
|
|
5329
5296
|
textToCopy: downloadCommand,
|
|
5330
5297
|
children: /* @__PURE__ */ jsx("div", {
|
|
5331
5298
|
className: "overflow-x-auto text-xs",
|
|
5332
|
-
children: downloadCommand.split("\n").map((
|
|
5299
|
+
children: keyedByContent(downloadCommand.split("\n"), (l) => l).map(({ key, item: line }) => /* @__PURE__ */ jsx("div", {
|
|
5333
5300
|
className: "whitespace-nowrap",
|
|
5334
5301
|
children: line
|
|
5335
|
-
},
|
|
5302
|
+
}, key))
|
|
5336
5303
|
})
|
|
5337
5304
|
});
|
|
5338
5305
|
})()] }) })] })]
|
|
@@ -5400,7 +5367,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5400
5367
|
}))]
|
|
5401
5368
|
}),
|
|
5402
5369
|
selectedNode.type === "file" && (() => {
|
|
5403
|
-
const
|
|
5370
|
+
const uris = getNodeUris(selectedNode);
|
|
5404
5371
|
const fileIcon = getNodeIcon(selectedNode, slots.storageIconUrl, t.chrome.storageIconAlt) ?? /* @__PURE__ */ jsx(FileIcon, { className: "h-5 w-5 theme-muted-text" });
|
|
5405
5372
|
const modifiedDt = selectedNode.modified ? DateTime.fromISO(selectedNode.modified) : null;
|
|
5406
5373
|
const createdDt = selectedNode.created ? DateTime.fromISO(selectedNode.created) : null;
|
|
@@ -5456,7 +5423,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5456
5423
|
className: "tabular-nums",
|
|
5457
5424
|
children: formatFileSize(selectedNode.size)
|
|
5458
5425
|
}),
|
|
5459
|
-
modifiedDt && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
5426
|
+
modifiedDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
5460
5427
|
"aria-hidden": true,
|
|
5461
5428
|
children: "·"
|
|
5462
5429
|
}), /* @__PURE__ */ jsxs("span", { children: ["Modified ", modifiedDt.toRelative()] })] })
|
|
@@ -5464,9 +5431,9 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5464
5431
|
})]
|
|
5465
5432
|
})]
|
|
5466
5433
|
}),
|
|
5467
|
-
|
|
5434
|
+
uris.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
5468
5435
|
className: "px-6 pt-5 space-y-2",
|
|
5469
|
-
children:
|
|
5436
|
+
children: uris.map((uri) => /* @__PURE__ */ jsx(UriCard, { value: uri }, uri))
|
|
5470
5437
|
}),
|
|
5471
5438
|
selectedNodeCanShare && /* @__PURE__ */ jsxs("div", {
|
|
5472
5439
|
className: "flex items-center gap-2 px-6 pt-4",
|
|
@@ -5486,21 +5453,21 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5486
5453
|
}), /* @__PURE__ */ jsxs("dl", {
|
|
5487
5454
|
className: "grid grid-cols-[minmax(0,140px)_1fr] gap-x-6 gap-y-2 text-sm",
|
|
5488
5455
|
children: [
|
|
5489
|
-
typeof selectedNode.size === "number" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5456
|
+
typeof selectedNode.size === "number" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5490
5457
|
className: "theme-muted-text",
|
|
5491
5458
|
children: t.chrome.size
|
|
5492
5459
|
}), /* @__PURE__ */ jsx("dd", {
|
|
5493
5460
|
className: "tabular-nums",
|
|
5494
5461
|
children: formatFileSize(selectedNode.size)
|
|
5495
5462
|
})] }),
|
|
5496
|
-
selectedNode.contentType && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5463
|
+
selectedNode.contentType && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5497
5464
|
className: "theme-muted-text",
|
|
5498
5465
|
children: "Content Type"
|
|
5499
5466
|
}), /* @__PURE__ */ jsx("dd", {
|
|
5500
5467
|
className: "font-mono text-xs",
|
|
5501
5468
|
children: selectedNode.contentType
|
|
5502
5469
|
})] }),
|
|
5503
|
-
createdDt && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5470
|
+
createdDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5504
5471
|
className: "theme-muted-text",
|
|
5505
5472
|
children: t.chrome.created
|
|
5506
5473
|
}), /* @__PURE__ */ jsxs("dd", { children: [
|
|
@@ -5511,7 +5478,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5511
5478
|
children: ["· ", createdDt.toRelative()]
|
|
5512
5479
|
})
|
|
5513
5480
|
] })] }),
|
|
5514
|
-
modifiedDt && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5481
|
+
modifiedDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5515
5482
|
className: "theme-muted-text",
|
|
5516
5483
|
children: "Last Modified"
|
|
5517
5484
|
}), /* @__PURE__ */ jsxs("dd", { children: [
|
|
@@ -5522,14 +5489,14 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5522
5489
|
children: ["· ", modifiedDt.toRelative()]
|
|
5523
5490
|
})
|
|
5524
5491
|
] })] }),
|
|
5525
|
-
selectedNode.hash && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5492
|
+
selectedNode.hash && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5526
5493
|
className: "theme-muted-text",
|
|
5527
5494
|
children: t.chrome.hash
|
|
5528
5495
|
}), /* @__PURE__ */ jsx("dd", {
|
|
5529
5496
|
className: "font-mono text-xs break-all",
|
|
5530
5497
|
children: selectedNode.hash.replace(/^"|"$/g, "")
|
|
5531
5498
|
})] }),
|
|
5532
|
-
selectedNode.storageClass && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5499
|
+
selectedNode.storageClass && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5533
5500
|
className: "theme-muted-text",
|
|
5534
5501
|
children: "Storage Class"
|
|
5535
5502
|
}), /* @__PURE__ */ jsx("dd", {
|