@parallelworks/ui 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -6
- package/dist/ai/adapter/memoryStore.d.ts +8 -0
- package/dist/ai/adapter/memoryStore.js +152 -0
- package/dist/ai/adapter/mock/index.d.ts +2 -0
- package/dist/ai/adapter/mock/index.js +3 -0
- package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
- package/dist/ai/adapter/mock/mockAdapter.js +81 -0
- package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
- package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
- package/dist/ai/adapter/openai/errors.d.ts +3 -0
- package/dist/ai/adapter/openai/errors.js +37 -0
- package/dist/ai/adapter/openai/index.d.ts +6 -0
- package/dist/ai/adapter/openai/index.js +6 -0
- package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
- package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
- package/dist/ai/adapter/openai/responses.d.ts +4 -0
- package/dist/ai/adapter/openai/responses.js +108 -0
- package/dist/ai/adapter/openai/sse.d.ts +24 -0
- package/dist/ai/adapter/openai/sse.js +112 -0
- package/dist/ai/adapter/openai/wire.d.ts +40 -0
- package/dist/ai/adapter/types.d.ts +131 -0
- package/dist/ai/adapter/types.js +11 -0
- package/dist/ai/agent/classifier.d.ts +2 -0
- package/dist/ai/agent/classifier.js +12 -0
- package/dist/ai/agent/diff.d.ts +35 -0
- package/dist/ai/agent/diff.js +223 -0
- package/dist/ai/agent/diffHighlight.d.ts +6 -0
- package/dist/ai/agent/diffHighlight.js +101 -0
- package/dist/ai/agent/index.d.ts +6 -0
- package/dist/ai/agent/permissions.d.ts +11 -0
- package/dist/ai/agent/permissions.js +29 -0
- package/dist/ai/agent/toolfmt.d.ts +18 -0
- package/dist/ai/agent/toolfmt.js +383 -0
- package/dist/ai/agent/transcript.d.ts +38 -0
- package/dist/ai/agent/transcript.js +127 -0
- package/dist/ai/ai.css +325 -0
- package/dist/ai/components/AllocationSelector.d.ts +4 -0
- package/dist/ai/components/AllocationSelector.js +56 -0
- package/dist/ai/components/AttachmentManager.d.ts +1 -0
- package/dist/ai/components/AttachmentManager.js +144 -0
- package/dist/ai/components/AttachmentUpload.d.ts +24 -0
- package/dist/ai/components/AttachmentUpload.js +179 -0
- package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
- package/dist/ai/components/BlockedGroupBanner.js +29 -0
- package/dist/ai/components/BranchNavigator.d.ts +8 -0
- package/dist/ai/components/BranchNavigator.js +57 -0
- package/dist/ai/components/ChatEmptyState.d.ts +3 -0
- package/dist/ai/components/ChatEmptyState.js +171 -0
- package/dist/ai/components/ChatInput.d.ts +45 -0
- package/dist/ai/components/ChatInput.js +265 -0
- package/dist/ai/components/ChatLayout.d.ts +5 -0
- package/dist/ai/components/ChatLayout.js +15 -0
- package/dist/ai/components/ChatMessage.d.ts +24 -0
- package/dist/ai/components/ChatMessage.js +338 -0
- package/dist/ai/components/ChatMessageList.d.ts +35 -0
- package/dist/ai/components/ChatMessageList.js +301 -0
- package/dist/ai/components/ChatSidebar.d.ts +5 -0
- package/dist/ai/components/ChatSidebar.js +345 -0
- package/dist/ai/components/ChatThread.d.ts +3 -0
- package/dist/ai/components/ChatThread.js +198 -0
- package/dist/ai/components/ComposerChrome.d.ts +18 -0
- package/dist/ai/components/ComposerChrome.js +50 -0
- package/dist/ai/components/ComposerFrame.d.ts +16 -0
- package/dist/ai/components/ComposerFrame.js +33 -0
- package/dist/ai/components/DragOverlay.d.ts +5 -0
- package/dist/ai/components/DragOverlay.js +20 -0
- package/dist/ai/components/ModelSelector.d.ts +6 -0
- package/dist/ai/components/ModelSelector.js +343 -0
- package/dist/ai/components/ShareDialog.d.ts +11 -0
- package/dist/ai/components/ShareDialog.js +260 -0
- package/dist/ai/components/SlashMenu.d.ts +28 -0
- package/dist/ai/components/SlashMenu.js +91 -0
- package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
- package/dist/ai/components/agent/ActivityLine.js +151 -0
- package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
- package/dist/ai/components/agent/AgentMessageParts.js +721 -0
- package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
- package/dist/ai/components/agent/PermissionPicker.js +38 -0
- package/dist/ai/components/attachmentKeys.d.ts +4 -0
- package/dist/ai/components/attachmentKeys.js +10 -0
- package/dist/ai/core/ChatProvider.d.ts +66 -0
- package/dist/ai/core/ChatProvider.js +848 -0
- package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
- package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
- package/dist/ai/core/chatReducer.d.ts +114 -0
- package/dist/ai/core/chatReducer.js +223 -0
- package/dist/ai/core/config.d.ts +47 -0
- package/dist/ai/core/config.js +45 -0
- package/dist/ai/core/events.d.ts +1 -0
- package/dist/ai/core/events.js +4 -0
- package/dist/ai/core/exportConversation.d.ts +4 -0
- package/dist/ai/core/exportConversation.js +42 -0
- package/dist/ai/core/greeting.d.ts +3 -0
- package/dist/ai/core/greeting.js +38 -0
- package/dist/ai/core/parts.d.ts +11 -0
- package/dist/ai/core/parts.js +84 -0
- package/dist/ai/core/useDragDrop.d.ts +14 -0
- package/dist/ai/core/useDragDrop.js +36 -0
- package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
- package/dist/ai/core/useKeyboardShortcuts.js +77 -0
- package/dist/ai/core/useSSEStream.d.ts +34 -0
- package/dist/ai/core/useSSEStream.js +98 -0
- package/dist/ai/index.d.ts +40 -0
- package/dist/ai/index.js +44 -0
- package/dist/ai/strings.d.ts +167 -0
- package/dist/ai/strings.js +175 -0
- package/dist/ai/types.d.ts +236 -0
- package/dist/ai/ui/Avatar.d.ts +8 -0
- package/dist/ai/ui/Avatar.js +35 -0
- package/dist/ai/ui/Dropdown.d.ts +31 -0
- package/dist/ai/ui/Dropdown.js +110 -0
- package/dist/ai/ui/Markdown.d.ts +8 -0
- package/dist/ai/ui/Markdown.js +79 -0
- package/dist/ai/ui/Notice.d.ts +21 -0
- package/dist/ai/ui/Notice.js +77 -0
- package/dist/ai/ui/buttons.d.ts +1 -0
- package/dist/ai/ui/codeBreaks.d.ts +16 -0
- package/dist/ai/ui/codeBreaks.js +36 -0
- package/dist/ai/ui/prose.d.ts +1 -0
- package/dist/ai/ui/prose.js +13 -0
- package/dist/ai/ui/sidebar.d.ts +81 -0
- package/dist/ai/ui/sidebar.js +148 -0
- package/dist/ai/utils.d.ts +4 -0
- package/dist/ai/utils.js +23 -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/components/AddMenu.js +46 -0
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +46 -0
- package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
- package/dist/components/Breadcrumbs.d.ts +1 -1
- package/dist/components/Breadcrumbs.js +83 -0
- package/dist/components/Button.js +83 -0
- package/dist/components/Callout.js +23 -0
- package/dist/components/Card.js +31 -0
- package/dist/components/CollapsiblePanel.js +77 -0
- package/dist/components/ConfirmModal.js +74 -0
- package/dist/components/CopyToClipboard.js +81 -0
- package/dist/{chunks/CreateModal-BY6DLmur.js → components/CreateModal.js} +10 -189
- package/dist/components/DescriptionList.js +42 -0
- package/dist/components/Dropdown.d.ts +1 -1
- package/dist/components/Dropdown.js +349 -0
- package/dist/components/EmptyState.d.ts +1 -1
- package/dist/components/EmptyState.js +27 -0
- package/dist/components/Filter.js +32 -0
- package/dist/components/FilterPill.js +13 -0
- package/dist/components/IconButton.js +34 -0
- package/dist/components/Indicator.d.ts +2 -2
- package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +88 -0
- package/dist/components/LetterBadge.d.ts +1 -1
- package/dist/components/LetterBadge.js +13 -0
- package/dist/components/Listbox.js +123 -0
- package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
- package/dist/components/Markdown.js +15 -0
- package/dist/components/MarkdownImpl.d.ts +1 -1
- package/dist/components/MultiSelect.js +171 -0
- package/dist/components/Overview.js +39 -0
- package/dist/components/PageHeader.js +194 -0
- package/dist/components/Pagination.js +35 -0
- package/dist/components/Provider.d.ts +16 -65
- package/dist/{chunks/Provider-Ghua9PkN.js → components/Provider.js} +54 -20
- package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
- package/dist/components/SectionHeader.js +11 -0
- package/dist/components/SettingModeOption.js +30 -0
- package/dist/components/SettingRow.d.ts +1 -1
- package/dist/components/SettingRow.js +35 -0
- package/dist/components/SettingsCard.js +31 -0
- package/dist/components/SettingsGroup.d.ts +1 -1
- package/dist/components/SettingsGroup.js +17 -0
- package/dist/components/Sort.js +42 -0
- package/dist/components/StatusBadge.js +63 -0
- package/dist/components/SwitchToggle.js +59 -0
- package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
- package/dist/{chunks/Table-4OHjdKdO.js → components/Table.js} +7 -5
- package/dist/components/Toggle.js +20 -0
- package/dist/{chunks/Tooltip-Crxo_eU_.js → components/Tooltip.js} +6 -4
- package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
- package/dist/components/focus.d.ts +2 -0
- package/dist/components/focus.js +7 -0
- package/dist/components/ghostButton.js +7 -0
- package/dist/components/inputClasses.js +4 -0
- package/dist/components/keys.d.ts +12 -0
- package/dist/components/keys.js +27 -0
- package/dist/components/relativeTime.js +32 -0
- package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
- package/dist/duration.d.ts +3 -0
- package/dist/duration.js +17 -0
- package/dist/editor/Editor.js +13 -0
- package/dist/editor/EditorField.js +53 -0
- package/dist/editor/FormCodePanel.js +173 -0
- package/dist/editor/Monaco.d.ts +1 -20
- package/dist/editor/Monaco.js +112 -0
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/index.js +7 -2
- package/dist/editor/themes.js +91 -0
- package/dist/editor/workers.js +29 -0
- package/dist/editor/yaml.d.ts +5 -0
- package/dist/editor/yaml.js +15 -0
- package/dist/engine.d.ts +98 -0
- package/dist/file-explorer/FileExplorer.d.ts +11 -2
- package/dist/file-explorer/FileExplorer.js +1402 -0
- package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
- package/dist/file-explorer/FileExplorerContext.js +370 -0
- package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
- package/dist/{chunks/CodePreviewEditor-4lx3kE-w.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
- package/dist/file-explorer/FilePreview/Inline.js +45 -0
- package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
- package/dist/file-explorer/FilePreview/Viewer.js +526 -0
- package/dist/file-explorer/FilePreview/index.js +266 -0
- package/dist/file-explorer/FilePreview/parse.js +63 -0
- package/dist/file-explorer/FilePreview/previewCache.js +98 -0
- package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
- package/dist/file-explorer/FilePreview/previewType.js +151 -0
- package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
- package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
- package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
- package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
- package/dist/file-explorer/FileRow.d.ts +1 -1
- package/dist/file-explorer/FileRow.js +165 -0
- package/dist/file-explorer/FileRowsWindow.js +92 -0
- package/dist/file-explorer/FileTable.js +172 -0
- package/dist/file-explorer/FileTableHeaders.js +49 -0
- package/dist/file-explorer/LoadMoreRow.js +46 -0
- package/dist/file-explorer/TreeView.js +376 -0
- package/dist/file-explorer/helpers.d.ts +4 -4
- package/dist/file-explorer/helpers.js +537 -0
- package/dist/file-explorer/index.d.ts +3 -3
- package/dist/file-explorer/index.js +7 -5705
- package/dist/file-explorer/lib/batch.js +39 -0
- package/dist/file-explorer/lib/errors.js +9 -0
- package/dist/file-explorer/lib/paging.js +124 -0
- package/dist/file-explorer/lib/types.d.ts +6 -1
- package/dist/file-explorer/lib/utils.d.ts +1 -3
- package/dist/file-explorer/lib/utils.js +322 -0
- package/dist/file-explorer/toasts.js +400 -0
- package/dist/file-explorer/treeRows.js +73 -0
- package/dist/file-explorer/useNearEnd.d.ts +1 -1
- package/dist/file-explorer/useNearEnd.js +27 -0
- package/dist/form/FieldWrapper.d.ts +2 -2
- package/dist/form/FieldWrapper.js +54 -0
- package/dist/form/Fields/BooleanField.js +51 -0
- package/dist/form/Fields/CheckboxGroupField.js +72 -0
- package/dist/form/Fields/ColorField.js +33 -0
- package/dist/form/Fields/DropdownField.js +49 -0
- package/dist/form/Fields/DurationField.js +130 -0
- package/dist/form/Fields/LabelField.js +19 -0
- package/dist/form/Fields/MultiDropdownField.js +40 -0
- package/dist/form/Fields/NumberField.js +51 -0
- package/dist/form/Fields/ObjectField.js +54 -0
- package/dist/form/Fields/RadioField.js +49 -0
- package/dist/form/Fields/RangeField.js +58 -0
- package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
- package/dist/form/Fields/ResourceOptionIcon.js +21 -0
- package/dist/form/Fields/StepField.js +6 -0
- package/dist/form/Fields/StringField.js +37 -0
- package/dist/form/Fields/TextAreaField.js +31 -0
- package/dist/form/Fields/durationValidity.js +11 -0
- package/dist/form/Form.d.ts +4 -21
- package/dist/form/Form.js +643 -0
- package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
- package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
- package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
- package/dist/form/Wizard/WizardContainer.js +138 -0
- package/dist/form/Wizard/WizardNavigation.js +92 -0
- package/dist/form/Wizard/WizardStepContent.js +35 -0
- package/dist/form/Wizard/WizardStepIndicator.js +108 -0
- package/dist/form/Wizard/useWizardState.js +118 -0
- package/dist/form/Wizard/utils.js +34 -0
- package/dist/form/fieldContext.js +26 -0
- package/dist/form/fieldRegistry.js +50 -0
- package/dist/form/index.d.ts +11 -12
- package/dist/form/index.js +18 -2445
- package/dist/form/lib.d.ts +6 -1
- package/dist/{chunks/FieldWrapper-Bef01EWO.js → form/lib.js} +5 -104
- package/dist/form/types/fieldComponentTypes.d.ts +3 -2
- package/dist/form/types/fieldTypes.d.ts +6 -20
- package/dist/form/useParsedOpts.d.ts +1 -1
- package/dist/form/useParsedOpts.js +62 -0
- package/dist/form/usePrevious.js +11 -0
- package/dist/form/utils/checkShowIfObject.js +14 -0
- package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
- package/dist/form/utils/duration.js +69 -0
- package/dist/form/utils/getParentValue.js +23 -0
- package/dist/form/utils/getPlaceholder.js +9 -0
- package/dist/form/utils/getValueUsingPath.js +23 -0
- package/dist/form/utils/secondaryField.js +22 -0
- package/dist/graph/AnnotationBanner.js +141 -0
- package/dist/graph/Collapse.js +41 -0
- package/dist/{workflow/index.js → graph/DependencyGraph.js} +76 -1163
- package/dist/graph/JobSummary.js +104 -0
- package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
- package/dist/graph/MatrixGroup.js +107 -0
- package/dist/graph/Reveal.js +57 -0
- package/dist/graph/TreeView.js +711 -0
- package/dist/{workflow → graph}/index.d.ts +1 -1
- package/dist/graph/index.js +2 -0
- package/dist/{workflow → graph}/types.d.ts +3 -3
- package/dist/graph/types.js +6 -0
- package/dist/icons.d.ts +20 -21
- package/dist/index.d.ts +38 -40
- package/dist/index.js +47 -599
- package/dist/list/ActionToolbar.js +184 -0
- package/dist/list/HoverCard.js +73 -0
- package/dist/list/ListPage.js +145 -0
- package/dist/list/ListPager.js +59 -0
- package/dist/list/ListTable.d.ts +1 -1
- package/dist/list/ListTable.js +163 -0
- package/dist/list/ListViewControls.d.ts +2 -2
- package/dist/list/ListViewControls.js +372 -0
- package/dist/list/RowContextMenu.js +350 -0
- package/dist/list/UserHoverCard.js +117 -0
- package/dist/list/index.d.ts +9 -9
- package/dist/list/index.js +11 -2
- package/dist/list/listSearch.js +137 -0
- package/dist/list/listView.js +210 -0
- package/dist/list/textEntry.js +10 -0
- package/dist/logviewer/LogViewer.d.ts +1 -1
- package/dist/logviewer/LogViewer.js +750 -0
- package/dist/logviewer/index.d.ts +1 -0
- package/dist/logviewer/index.js +3 -742
- package/dist/logviewer/segments.d.ts +9 -0
- package/dist/logviewer/segments.js +80 -0
- package/dist/styles/fonts.css +4 -4
- package/dist/styles/theme.css +6 -4
- package/dist/styles.css +1 -1
- package/dist/theme/index.js +2 -2
- package/package.json +38 -14
- package/LICENSE +0 -202
- package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
- package/dist/chunks/PageHeader-l49p1nM9.js +0 -690
- package/dist/chunks/SwitchToggle-CHPP8uZQ.js +0 -755
- package/dist/chunks/editor-9_uVtUyy.js +0 -347
- package/dist/chunks/list-qrojZZNv.js +0 -1411
- package/dist/editor/usesGitlab.d.ts +0 -97
- package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
- package/dist/form/types/workflow-schema.d.ts +0 -1145
- package/dist/form/utils/pwResolver.d.ts +0 -18
- package/dist/{chunks/MarkdownImpl-Bq4QhrtA.js → components/MarkdownImpl.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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/file-explorer/lib/batch.ts
|
|
2
|
+
async function* paginate(fetchPage, nextCursor) {
|
|
3
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4
|
+
let cursor;
|
|
5
|
+
while (true) {
|
|
6
|
+
const page = await fetchPage(cursor);
|
|
7
|
+
yield page;
|
|
8
|
+
cursor = nextCursor(page);
|
|
9
|
+
if (!cursor || seen.has(cursor)) return;
|
|
10
|
+
seen.add(cursor);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function chunk(items, size) {
|
|
14
|
+
const chunks = [];
|
|
15
|
+
for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size));
|
|
16
|
+
return chunks;
|
|
17
|
+
}
|
|
18
|
+
async function mapWithConcurrency(items, limit, worker) {
|
|
19
|
+
const results = new Array(items.length);
|
|
20
|
+
const queue = items.entries();
|
|
21
|
+
const run = async () => {
|
|
22
|
+
for (const [index, item] of queue) try {
|
|
23
|
+
results[index] = {
|
|
24
|
+
status: "fulfilled",
|
|
25
|
+
value: await worker(item)
|
|
26
|
+
};
|
|
27
|
+
} catch (reason) {
|
|
28
|
+
results[index] = {
|
|
29
|
+
status: "rejected",
|
|
30
|
+
reason
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => run());
|
|
35
|
+
await Promise.all(workers);
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { chunk, mapWithConcurrency, paginate };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ensureTrailingSlash, getNodeName } from "./utils.js";
|
|
2
|
+
//#region src/file-explorer/lib/paging.ts
|
|
3
|
+
/** Both providers already cap an unqualified list at 1,000, so stating it leaves
|
|
4
|
+
* single-page directories byte-identical. */
|
|
5
|
+
var LIST_PAGE_SIZE = 1e3;
|
|
6
|
+
function isListed(listings, path) {
|
|
7
|
+
return (listings.get(path)?.pagesLoaded ?? 0) > 0;
|
|
8
|
+
}
|
|
9
|
+
/** True even while a request is in flight, so the sentinel stays put. */
|
|
10
|
+
function hasMore(listings, path) {
|
|
11
|
+
return !!listings.get(path)?.cursor;
|
|
12
|
+
}
|
|
13
|
+
/** A failed page needs an explicit retry, so it stops auto-loading here. */
|
|
14
|
+
function canLoadMore(listings, path) {
|
|
15
|
+
const listing = listings.get(path);
|
|
16
|
+
return !!listing?.cursor && !listing.loadingMore && !listing.error;
|
|
17
|
+
}
|
|
18
|
+
/** Auto-load stops at the cap; past it the user asks for each page. */
|
|
19
|
+
function canAutoLoadMore(listings, path) {
|
|
20
|
+
return canLoadMore(listings, path) && (listings.get(path)?.pagesLoaded ?? 0) < 5;
|
|
21
|
+
}
|
|
22
|
+
function isLoadingMore(listings, path) {
|
|
23
|
+
return listings.get(path)?.loadingMore === true;
|
|
24
|
+
}
|
|
25
|
+
function loadMoreError(listings, path) {
|
|
26
|
+
return listings.get(path)?.error;
|
|
27
|
+
}
|
|
28
|
+
function beginLoadingMore(listings, path) {
|
|
29
|
+
const listing = listings.get(path);
|
|
30
|
+
if (!listing) return listings;
|
|
31
|
+
return new Map(listings).set(path, {
|
|
32
|
+
...listing,
|
|
33
|
+
loadingMore: true,
|
|
34
|
+
error: void 0
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function advanceListing(listings, path, page) {
|
|
38
|
+
const listing = listings.get(path);
|
|
39
|
+
return new Map(listings).set(path, {
|
|
40
|
+
cursor: page.cursor,
|
|
41
|
+
pagesLoaded: (listing?.pagesLoaded ?? 0) + 1,
|
|
42
|
+
loadingMore: false,
|
|
43
|
+
entries: [...listing?.entries ?? [], ...page.entries]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Subfolders the page no longer names lose their records, or a re-created folder
|
|
47
|
+
* would show old contents; the rest keep theirs, since a refresh lists them
|
|
48
|
+
* alongside this one and a parent landing later must not undo a child's page. */
|
|
49
|
+
function replaceListing(listings, path, page) {
|
|
50
|
+
const prefix = ensureTrailingSlash(path);
|
|
51
|
+
const subfolders = new Set(page.entries.filter((entry) => entry.type === "directory").map(getNodeName));
|
|
52
|
+
const next = new Map(listings);
|
|
53
|
+
next.delete(path);
|
|
54
|
+
for (const key of listings.keys()) {
|
|
55
|
+
if (!key.startsWith(prefix)) continue;
|
|
56
|
+
if (!subfolders.has(key.slice(prefix.length).split("/")[0] ?? "")) next.delete(key);
|
|
57
|
+
}
|
|
58
|
+
return advanceListing(next, path, page);
|
|
59
|
+
}
|
|
60
|
+
/** `pagesLoaded` stays put, so a failed first page still reads as unlisted. */
|
|
61
|
+
function recordListingError(listings, path, error) {
|
|
62
|
+
const listing = listings.get(path);
|
|
63
|
+
return new Map(listings).set(path, {
|
|
64
|
+
cursor: listing?.cursor,
|
|
65
|
+
pagesLoaded: listing?.pagesLoaded ?? 0,
|
|
66
|
+
loadingMore: false,
|
|
67
|
+
error,
|
|
68
|
+
entries: listing?.entries ?? []
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Keeps the cursor and pages, so a retry resumes paging instead of starting over. */
|
|
72
|
+
function clearListingError(listings, path) {
|
|
73
|
+
const listing = listings.get(path);
|
|
74
|
+
if (!listing) return listings;
|
|
75
|
+
return new Map(listings).set(path, {
|
|
76
|
+
...listing,
|
|
77
|
+
error: void 0
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function clearListingsUnder(listings, path) {
|
|
81
|
+
const prefix = ensureTrailingSlash(path);
|
|
82
|
+
const next = new Map(listings);
|
|
83
|
+
for (const key of listings.keys()) if (key === path || key.startsWith(prefix)) next.delete(key);
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
/** Tree paths whose children should keep arrival order, since sorting them would
|
|
87
|
+
* reshuffle rows under the user mid-scroll. */
|
|
88
|
+
function incompletePaths(listings) {
|
|
89
|
+
const paths = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const [path, listing] of listings) if (listing.cursor) paths.add(path);
|
|
91
|
+
return paths;
|
|
92
|
+
}
|
|
93
|
+
/** What a deep link needs next. Pure so the walk is table-testable. */
|
|
94
|
+
function nextHydrationStep({ targetResolved, ancestors, isListableDir, listings, inFlight, pagesPulled }) {
|
|
95
|
+
for (const path of ancestors) {
|
|
96
|
+
if (!isListableDir(path)) continue;
|
|
97
|
+
if (inFlight.has(path)) return { kind: "done" };
|
|
98
|
+
if (!listings.has(path)) return {
|
|
99
|
+
kind: "fetch",
|
|
100
|
+
path
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (targetResolved) return { kind: "done" };
|
|
104
|
+
const deepest = [...ancestors].reverse().find(isListableDir);
|
|
105
|
+
if (!deepest) return { kind: "done" };
|
|
106
|
+
if (!hasMore(listings, deepest)) return {
|
|
107
|
+
kind: "stuck",
|
|
108
|
+
path: deepest
|
|
109
|
+
};
|
|
110
|
+
if (loadMoreError(listings, deepest)) return {
|
|
111
|
+
kind: "stuck",
|
|
112
|
+
path: deepest
|
|
113
|
+
};
|
|
114
|
+
if (pagesPulled >= 20) return {
|
|
115
|
+
kind: "stuck",
|
|
116
|
+
path: deepest
|
|
117
|
+
};
|
|
118
|
+
return isLoadingMore(listings, deepest) ? { kind: "done" } : {
|
|
119
|
+
kind: "more",
|
|
120
|
+
path: deepest
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { LIST_PAGE_SIZE, advanceListing, beginLoadingMore, canAutoLoadMore, clearListingError, clearListingsUnder, hasMore, incompletePaths, isListed, isLoadingMore, loadMoreError, nextHydrationStep, recordListingError, replaceListing };
|
|
@@ -15,8 +15,13 @@ export type TStorage = {
|
|
|
15
15
|
canUpload?: boolean | undefined;
|
|
16
16
|
canShare?: boolean | undefined;
|
|
17
17
|
canManageAccess?: boolean | undefined;
|
|
18
|
-
supportsPwUri?: boolean | undefined;
|
|
19
18
|
};
|
|
19
|
+
/** A file the host is asked to name or download: its storage, key and name. */
|
|
20
|
+
export interface ExplorerObject {
|
|
21
|
+
storage: TStorage;
|
|
22
|
+
key: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}
|
|
20
25
|
export type TStorageObject = {
|
|
21
26
|
path: string;
|
|
22
27
|
type: 'file' | 'directory';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IFileExplorerClient } from './fileExplorer.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TreeMap, TreeNode, TStorage, TStorageObject, UploadNode } from './types.js';
|
|
3
3
|
export declare function formatFileSize(bytes: number): string;
|
|
4
4
|
export declare function getFileExtension(filename: string): string | null;
|
|
5
5
|
export declare function getNodeName(node: TStorageObject): string;
|
|
@@ -33,8 +33,6 @@ export declare function attachmentDisposition(filename: string): string;
|
|
|
33
33
|
export declare function getDirectoryPrefixFromNode(node: TreeNode): string;
|
|
34
34
|
export declare function pruneNodesInsideSelectedDirectories(nodes: TreeNode[]): TreeNode[];
|
|
35
35
|
export declare function collectObjectKeysToDelete(nodes: TreeNode[], listKeysUnderPrefix: (prefix: string) => Promise<string[]>): Promise<string[]>;
|
|
36
|
-
export declare function getPwUri(username: string, pwName: string, key: string): string;
|
|
37
|
-
export declare function getCspUri(csp: string, cspBucketName: string, key: string): string | null;
|
|
38
36
|
export declare function getUriScheme(uri: string): string;
|
|
39
37
|
export declare function removeLeadingSlash(path: string): string;
|
|
40
38
|
export declare function removeLeadingAndTrailingSlashes(path: string): string;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
//#region src/file-explorer/lib/utils.ts
|
|
2
|
+
function formatFileSize(bytes) {
|
|
3
|
+
if (bytes === 0) return "0 B";
|
|
4
|
+
const k = 1024;
|
|
5
|
+
const sizes = [
|
|
6
|
+
"B",
|
|
7
|
+
"KB",
|
|
8
|
+
"MB",
|
|
9
|
+
"GB",
|
|
10
|
+
"TB"
|
|
11
|
+
];
|
|
12
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
13
|
+
return Number.parseFloat((bytes / k ** i).toFixed(2)) + " " + sizes[i];
|
|
14
|
+
}
|
|
15
|
+
function getFileExtension(filename) {
|
|
16
|
+
return filename.match(/\.([a-zA-Z0-9]+)$/)?.[1]?.toLowerCase() ?? null;
|
|
17
|
+
}
|
|
18
|
+
function getNodeName(node) {
|
|
19
|
+
const parts = removeTrailingSlash(node.path || "").split("/");
|
|
20
|
+
return parts[parts.length - 1] ?? "";
|
|
21
|
+
}
|
|
22
|
+
function toNode(entry, storage, storagePath, storageName) {
|
|
23
|
+
const relativePath = removeLeadingSlash(entry.path);
|
|
24
|
+
const isDirectory = entry.type === "directory";
|
|
25
|
+
return {
|
|
26
|
+
name: getNodeName(entry),
|
|
27
|
+
path: isDirectory ? ensureTrailingSlash(`${storagePath}${removeTrailingSlash(relativePath)}`) : `${storagePath}${relativePath}`,
|
|
28
|
+
relativePath: entry.key || (isDirectory ? entry.path : relativePath),
|
|
29
|
+
type: entry.type,
|
|
30
|
+
size: entry.size,
|
|
31
|
+
created: entry.created,
|
|
32
|
+
modified: entry.modified,
|
|
33
|
+
contentType: entry.contentType,
|
|
34
|
+
hash: entry.hash,
|
|
35
|
+
storageClass: entry.storageClass,
|
|
36
|
+
storageId: storage.id,
|
|
37
|
+
storageName
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function compareNodes(a, b) {
|
|
41
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
42
|
+
return (a.displayName || a.name).localeCompare(b.displayName || b.name);
|
|
43
|
+
}
|
|
44
|
+
function extendsEntries(next, previous) {
|
|
45
|
+
return next.length >= previous.length && previous.every((entry, i) => next[i] === entry);
|
|
46
|
+
}
|
|
47
|
+
function childrenOf(dir, sorted) {
|
|
48
|
+
if (sorted) {
|
|
49
|
+
dir.sorted ??= [...dir.directories, ...dir.files].sort(compareNodes);
|
|
50
|
+
return dir.sorted;
|
|
51
|
+
}
|
|
52
|
+
dir.unsorted ??= [...dir.directories, ...dir.files];
|
|
53
|
+
return dir.unsorted;
|
|
54
|
+
}
|
|
55
|
+
/** One accumulator per listed directory, so a landed page costs the page and not
|
|
56
|
+
* the tree. One builder per explorer instance. */
|
|
57
|
+
function createTreeBuilder() {
|
|
58
|
+
const directories = /* @__PURE__ */ new Map();
|
|
59
|
+
let last = null;
|
|
60
|
+
function unchanged(storages, options) {
|
|
61
|
+
if (!last || last.storages !== storages || last.options.forceUserHierarchy !== options.forceUserHierarchy || last.options.unsortedParents !== options.unsortedParents) return false;
|
|
62
|
+
const before = last.options.listings;
|
|
63
|
+
if (options.listings.size !== before.size) return false;
|
|
64
|
+
for (const [dirPath, listing] of options.listings) if (before.get(dirPath)?.entries !== listing.entries) return false;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
function nodesFor(dirPath, storage, storagePath, storageName, entries) {
|
|
68
|
+
const cached = directories.get(dirPath);
|
|
69
|
+
const reusable = cached?.storageName === storageName ? cached : void 0;
|
|
70
|
+
if (reusable && reusable.entries === entries) return reusable;
|
|
71
|
+
let dir;
|
|
72
|
+
if (reusable && extendsEntries(entries, reusable.entries)) dir = reusable;
|
|
73
|
+
else {
|
|
74
|
+
dir = {
|
|
75
|
+
entries: [],
|
|
76
|
+
storageName,
|
|
77
|
+
directories: [],
|
|
78
|
+
files: []
|
|
79
|
+
};
|
|
80
|
+
directories.set(dirPath, dir);
|
|
81
|
+
}
|
|
82
|
+
for (let i = dir.entries.length; i < entries.length; i++) {
|
|
83
|
+
const node = toNode(entries[i], storage, storagePath, storageName);
|
|
84
|
+
if (node.path === dirPath) continue;
|
|
85
|
+
if (node.type === "directory") dir.directories.push(node);
|
|
86
|
+
else dir.files.push(node);
|
|
87
|
+
}
|
|
88
|
+
dir.entries = entries;
|
|
89
|
+
dir.unsorted = void 0;
|
|
90
|
+
dir.sorted = void 0;
|
|
91
|
+
return dir;
|
|
92
|
+
}
|
|
93
|
+
function addStorage(storage, storagePath, options, tree) {
|
|
94
|
+
const storageName = storage.bucketName || storage.name;
|
|
95
|
+
const root = {
|
|
96
|
+
name: storage.name,
|
|
97
|
+
displayName: storage.displayName,
|
|
98
|
+
path: storagePath,
|
|
99
|
+
relativePath: "",
|
|
100
|
+
type: "directory",
|
|
101
|
+
created: storage.created,
|
|
102
|
+
modified: storage.modified,
|
|
103
|
+
storageId: storage.id,
|
|
104
|
+
storageName,
|
|
105
|
+
storageNodeIcon: storage.type,
|
|
106
|
+
storageImageUrl: storage.imageUrl,
|
|
107
|
+
storageType: storage.type,
|
|
108
|
+
root: true
|
|
109
|
+
};
|
|
110
|
+
tree.treeMap[storagePath] = root;
|
|
111
|
+
for (const [dirPath, listing] of options.listings) {
|
|
112
|
+
if (!dirPath.startsWith(storagePath)) continue;
|
|
113
|
+
const children = childrenOf(nodesFor(dirPath, storage, storagePath, storageName, listing.entries), !options.unsortedParents?.has(dirPath));
|
|
114
|
+
if (children.length === 0) continue;
|
|
115
|
+
for (const node of children) tree.treeMap[node.path] = node;
|
|
116
|
+
tree.parentToChildrenMap[dirPath] = children;
|
|
117
|
+
}
|
|
118
|
+
return root;
|
|
119
|
+
}
|
|
120
|
+
return (storages, options) => {
|
|
121
|
+
if (last && unchanged(storages, options)) return last.tree;
|
|
122
|
+
const tree = {
|
|
123
|
+
treeMap: {},
|
|
124
|
+
parentToChildrenMap: {},
|
|
125
|
+
rootNodes: []
|
|
126
|
+
};
|
|
127
|
+
if (storages.length === 1 && !options.forceUserHierarchy) {
|
|
128
|
+
const storage = storages[0];
|
|
129
|
+
const storagePath = ensureTrailingSlash(storage.user ? `${storage.user}/${storage.name}` : storage.name);
|
|
130
|
+
tree.rootNodes.push(addStorage(storage, storagePath, options, tree));
|
|
131
|
+
} else {
|
|
132
|
+
const userGroups = {};
|
|
133
|
+
for (const storage of storages) {
|
|
134
|
+
const username = storage.user ?? "";
|
|
135
|
+
if (!userGroups[username]) userGroups[username] = [];
|
|
136
|
+
userGroups[username].push(storage);
|
|
137
|
+
}
|
|
138
|
+
for (const username of Object.keys(userGroups)) {
|
|
139
|
+
const userPath = ensureTrailingSlash(username);
|
|
140
|
+
const userNode = {
|
|
141
|
+
name: username,
|
|
142
|
+
path: userPath,
|
|
143
|
+
type: "directory",
|
|
144
|
+
displayName: username
|
|
145
|
+
};
|
|
146
|
+
tree.treeMap[userPath] = userNode;
|
|
147
|
+
tree.rootNodes.push(userNode);
|
|
148
|
+
const storageRoots = (userGroups[username] ?? []).map((storage) => addStorage(storage, ensureTrailingSlash(`${userPath}${removeLeadingAndTrailingSlashes(storage.name)}`), options, tree));
|
|
149
|
+
tree.parentToChildrenMap[userPath] = storageRoots.sort(compareNodes);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const dirPath of directories.keys()) if (!options.listings.has(dirPath)) directories.delete(dirPath);
|
|
153
|
+
last = {
|
|
154
|
+
storages,
|
|
155
|
+
options,
|
|
156
|
+
tree
|
|
157
|
+
};
|
|
158
|
+
return tree;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function getNodeChildren(parentToChildrenMap, parentPath) {
|
|
162
|
+
return parentToChildrenMap[parentPath.endsWith("/") ? parentPath : parentPath + "/"] || [];
|
|
163
|
+
}
|
|
164
|
+
function getParentPath(currentPath) {
|
|
165
|
+
return ensureTrailingSlash(removeLastPathSegment(currentPath));
|
|
166
|
+
}
|
|
167
|
+
function getDirectoryNameError(name, siblings) {
|
|
168
|
+
if (name === "." || name === "..") return "reserved";
|
|
169
|
+
if (name.includes("/") || name.includes("\\")) return "separator";
|
|
170
|
+
if (siblings.some((node) => node.name === name)) return "exists";
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
function getDirectoryObjectPath(parentNode, directoryName) {
|
|
174
|
+
const parentPath = parentNode.root ? "" : removeLeadingAndTrailingSlashes(parentNode.relativePath ?? "");
|
|
175
|
+
return parentPath ? `${parentPath}/${directoryName}/` : `${directoryName}/`;
|
|
176
|
+
}
|
|
177
|
+
async function getUploadNodesFromDataTransferItems(items) {
|
|
178
|
+
const uploadNodes = [];
|
|
179
|
+
let totalSize = 0;
|
|
180
|
+
async function traverse(entry, path = "") {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
if (entry.isFile) entry.file((file) => {
|
|
183
|
+
uploadNodes.push({
|
|
184
|
+
name: file.name,
|
|
185
|
+
file,
|
|
186
|
+
relativePath: path ? path + file.name : file.name
|
|
187
|
+
});
|
|
188
|
+
totalSize += file.size;
|
|
189
|
+
resolve();
|
|
190
|
+
}, reject);
|
|
191
|
+
else if (entry.isDirectory) entry.createReader().readEntries(async (entries) => {
|
|
192
|
+
const newPath = path ? path + entry.name + "/" : entry.name + "/";
|
|
193
|
+
if (entries.length === 0) uploadNodes.push({
|
|
194
|
+
name: entry.name,
|
|
195
|
+
file: new Blob(),
|
|
196
|
+
relativePath: newPath
|
|
197
|
+
});
|
|
198
|
+
else for (const entry of entries) await traverse(entry, newPath);
|
|
199
|
+
resolve();
|
|
200
|
+
}, reject);
|
|
201
|
+
else resolve();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const entries = [];
|
|
205
|
+
for (let i = 0; i < items.length; i++) {
|
|
206
|
+
const entry = items[i]?.webkitGetAsEntry?.();
|
|
207
|
+
if (entry) entries.push(entry);
|
|
208
|
+
}
|
|
209
|
+
for (const entry of entries) await traverse(entry);
|
|
210
|
+
return {
|
|
211
|
+
uploadNodes,
|
|
212
|
+
totalSize
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function calculateUploadNodesTotalSize(uploadNodes) {
|
|
216
|
+
return uploadNodes.reduce((total, node) => {
|
|
217
|
+
return total + (node.file?.size || 0);
|
|
218
|
+
}, 0);
|
|
219
|
+
}
|
|
220
|
+
function getObjectKeyFromNode(node) {
|
|
221
|
+
return node.relativePath || node.path.split("/").filter(Boolean).slice(1).join("/");
|
|
222
|
+
}
|
|
223
|
+
function attachmentDisposition(filename) {
|
|
224
|
+
return `attachment; filename="${filename.replace(/[^\x20-\x7e]|["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(filename).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
|
|
225
|
+
}
|
|
226
|
+
function getDirectoryPrefixFromNode(node) {
|
|
227
|
+
const key = getObjectKeyFromNode(node);
|
|
228
|
+
if (!key) throw new Error(`Cannot resolve an object prefix for ${node.path}`);
|
|
229
|
+
return ensureTrailingSlash(key);
|
|
230
|
+
}
|
|
231
|
+
function pruneNodesInsideSelectedDirectories(nodes) {
|
|
232
|
+
const directoryPaths = nodes.filter((node) => node.type === "directory").map((node) => ensureTrailingSlash(node.path));
|
|
233
|
+
return nodes.filter((node) => !directoryPaths.some((directoryPath) => node.path !== directoryPath && node.path.startsWith(directoryPath)));
|
|
234
|
+
}
|
|
235
|
+
async function collectObjectKeysToDelete(nodes, listKeysUnderPrefix) {
|
|
236
|
+
const keys = /* @__PURE__ */ new Set();
|
|
237
|
+
for (const node of nodes) {
|
|
238
|
+
if (node.type !== "directory") {
|
|
239
|
+
keys.add(getObjectKeyFromNode(node));
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const prefix = getDirectoryPrefixFromNode(node);
|
|
243
|
+
const found = await listKeysUnderPrefix(prefix);
|
|
244
|
+
for (const key of found.filter((key) => key.startsWith(prefix))) keys.add(key);
|
|
245
|
+
}
|
|
246
|
+
return Array.from(keys);
|
|
247
|
+
}
|
|
248
|
+
function getUriScheme(uri) {
|
|
249
|
+
return uri.match(/^([a-z0-9+.-]+):\/\//i)?.[1] ?? "";
|
|
250
|
+
}
|
|
251
|
+
function removeLeadingSlash(path) {
|
|
252
|
+
return path.replace(/^\/+/, "");
|
|
253
|
+
}
|
|
254
|
+
function removeTrailingSlash(path) {
|
|
255
|
+
return path.replace(/\/$/, "");
|
|
256
|
+
}
|
|
257
|
+
function removeLeadingAndTrailingSlashes(path) {
|
|
258
|
+
return path.replace(/^\/+|\/+$/g, "");
|
|
259
|
+
}
|
|
260
|
+
function removeLastPathSegment(path) {
|
|
261
|
+
return path.replace(/\/[^/]+\/?$/, "");
|
|
262
|
+
}
|
|
263
|
+
function ensureTrailingSlash(path) {
|
|
264
|
+
return path.endsWith("/") ? path : path + "/";
|
|
265
|
+
}
|
|
266
|
+
function isClientValid(client) {
|
|
267
|
+
if (!client?.getExpiresAt) return false;
|
|
268
|
+
const expiresAt = client.getExpiresAt();
|
|
269
|
+
return expiresAt && expiresAt > /* @__PURE__ */ new Date();
|
|
270
|
+
}
|
|
271
|
+
function getUploadSessionETA(totalLoadedBytes, totalSizeBytes, lastProgressTime, lastProgressBytes) {
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
const MAX_ETA_SECONDS = 86400;
|
|
274
|
+
if (!lastProgressTime || !lastProgressBytes) return {
|
|
275
|
+
lastProgressTime: now,
|
|
276
|
+
lastProgressBytes: totalLoadedBytes,
|
|
277
|
+
shouldUpdateETA: false
|
|
278
|
+
};
|
|
279
|
+
const timeDiff = now - lastProgressTime;
|
|
280
|
+
const bytesDiff = totalLoadedBytes - lastProgressBytes;
|
|
281
|
+
if (timeDiff < 1e3) return {
|
|
282
|
+
lastProgressTime,
|
|
283
|
+
lastProgressBytes,
|
|
284
|
+
shouldUpdateETA: false
|
|
285
|
+
};
|
|
286
|
+
if (bytesDiff > 0 && timeDiff > 0) {
|
|
287
|
+
const bytesPerSecond = bytesDiff / timeDiff * 1e3;
|
|
288
|
+
const remainingBytes = totalSizeBytes - totalLoadedBytes;
|
|
289
|
+
if (remainingBytes <= 0) return {
|
|
290
|
+
estimatedSecondsLeft: 0,
|
|
291
|
+
lastProgressTime: now,
|
|
292
|
+
lastProgressBytes: totalLoadedBytes,
|
|
293
|
+
shouldUpdateETA: true
|
|
294
|
+
};
|
|
295
|
+
let eta = Math.ceil(remainingBytes / bytesPerSecond);
|
|
296
|
+
eta = Math.max(1, Math.min(eta, MAX_ETA_SECONDS));
|
|
297
|
+
return {
|
|
298
|
+
estimatedSecondsLeft: eta,
|
|
299
|
+
lastProgressTime: now,
|
|
300
|
+
lastProgressBytes: totalLoadedBytes,
|
|
301
|
+
shouldUpdateETA: true
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
lastProgressTime,
|
|
306
|
+
lastProgressBytes,
|
|
307
|
+
shouldUpdateETA: false
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function formatETA(seconds) {
|
|
311
|
+
if (seconds <= 0) return "Almost done";
|
|
312
|
+
if (seconds < 5) return "Few seconds";
|
|
313
|
+
if (seconds < 60) return `~ ${seconds}s`;
|
|
314
|
+
if (seconds < 3600) {
|
|
315
|
+
const mins = Math.floor(seconds / 60);
|
|
316
|
+
const secs = seconds % 60;
|
|
317
|
+
return secs > 0 ? `~ ${mins}m ${secs}s` : `~ ${mins}m`;
|
|
318
|
+
}
|
|
319
|
+
return `~ ${Math.floor(seconds / 3600)}h ${Math.ceil(seconds % 3600 / 60)}m`;
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
export { attachmentDisposition, calculateUploadNodesTotalSize, collectObjectKeysToDelete, createTreeBuilder, ensureTrailingSlash, formatETA, formatFileSize, getDirectoryNameError, getDirectoryObjectPath, getDirectoryPrefixFromNode, getFileExtension, getNodeChildren, getNodeName, getObjectKeyFromNode, getParentPath, getUploadNodesFromDataTransferItems, getUploadSessionETA, getUriScheme, isClientValid, pruneNodesInsideSelectedDirectories, removeLeadingAndTrailingSlashes, removeLeadingSlash };
|