@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,196 @@
|
|
|
1
|
+
//#region src/file-explorer/FilePreview/readCappedBytes.ts
|
|
2
|
+
/** Read a Response body but stop once it passes `maxBytes` (returning `tooLarge`), so an object whose listed size is missing or under-reported can't be pulled fully into the tab. */
|
|
3
|
+
async function readCappedBytes(response, maxBytes) {
|
|
4
|
+
const reader = response.body?.getReader();
|
|
5
|
+
if (!reader) {
|
|
6
|
+
const header = response.headers.get("content-length");
|
|
7
|
+
const declared = header === null ? NaN : Number(header);
|
|
8
|
+
if (!Number.isFinite(declared) || declared > maxBytes) return {
|
|
9
|
+
bytes: /* @__PURE__ */ new Uint8Array(0),
|
|
10
|
+
tooLarge: true
|
|
11
|
+
};
|
|
12
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
13
|
+
return buffer.byteLength > maxBytes ? {
|
|
14
|
+
bytes: /* @__PURE__ */ new Uint8Array(0),
|
|
15
|
+
tooLarge: true
|
|
16
|
+
} : {
|
|
17
|
+
bytes: buffer,
|
|
18
|
+
tooLarge: false
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
|
|
24
|
+
total += chunk.value.byteLength;
|
|
25
|
+
if (total > maxBytes) {
|
|
26
|
+
await reader.cancel();
|
|
27
|
+
return {
|
|
28
|
+
bytes: /* @__PURE__ */ new Uint8Array(0),
|
|
29
|
+
tooLarge: true
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
chunks.push(chunk.value);
|
|
33
|
+
}
|
|
34
|
+
const bytes = new Uint8Array(total);
|
|
35
|
+
let offset = 0;
|
|
36
|
+
for (const chunk of chunks) {
|
|
37
|
+
bytes.set(chunk, offset);
|
|
38
|
+
offset += chunk.byteLength;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
bytes,
|
|
42
|
+
tooLarge: false
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function countNewlines(text) {
|
|
46
|
+
let count = 0;
|
|
47
|
+
for (let i = 0; i < text.length; i++) if (text[i] === "\n") count++;
|
|
48
|
+
return count;
|
|
49
|
+
}
|
|
50
|
+
/** Lines a preview will render; a trailing newline doesn't open another line. */
|
|
51
|
+
function countLines(text) {
|
|
52
|
+
if (text === "") return 0;
|
|
53
|
+
return countNewlines(text) + (text.endsWith("\n") ? 0 : 1);
|
|
54
|
+
}
|
|
55
|
+
function capLines(text, maxLines) {
|
|
56
|
+
if (maxLines === void 0) return text;
|
|
57
|
+
let count = 0;
|
|
58
|
+
for (let i = 0; i < text.length; i++) if (text[i] === "\n") {
|
|
59
|
+
count++;
|
|
60
|
+
if (count >= maxLines) return text.slice(0, i + 1);
|
|
61
|
+
}
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
/** Reading one line past the cap is what makes `truncated` exact: a body that ends
|
|
65
|
+
* right at `maxLines` is complete, not cut short. */
|
|
66
|
+
function capToLines(text, maxLines) {
|
|
67
|
+
if (maxLines === void 0) return {
|
|
68
|
+
text,
|
|
69
|
+
tooLarge: false,
|
|
70
|
+
truncated: false
|
|
71
|
+
};
|
|
72
|
+
const capped = capLines(text, maxLines);
|
|
73
|
+
return {
|
|
74
|
+
text: capped,
|
|
75
|
+
tooLarge: false,
|
|
76
|
+
truncated: capped.length < text.length
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** What a read yields when the byte cap trips mid-stream. Complete lines already read
|
|
80
|
+
* are a usable preview, so the cap cuts them short rather than refusing; only a body
|
|
81
|
+
* with no line break leaves nothing to show. */
|
|
82
|
+
function capAtBytes(text, lines, maxLines) {
|
|
83
|
+
if (maxLines === void 0 || lines === 0) return {
|
|
84
|
+
text: "",
|
|
85
|
+
tooLarge: true,
|
|
86
|
+
truncated: false
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
text: capLines(text, lines),
|
|
90
|
+
tooLarge: false,
|
|
91
|
+
truncated: true
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Read a Response as text capped at `maxBytes`; when `maxLines` is given, cancel the stream
|
|
95
|
+
* once that many `\n` arrive. Cutting on `\n` keeps the returned prefix on a clean UTF-8 boundary. */
|
|
96
|
+
async function readCappedText(response, maxBytes, maxLines) {
|
|
97
|
+
const probeLines = maxLines === void 0 ? void 0 : maxLines + 1;
|
|
98
|
+
const reader = response.body?.getReader();
|
|
99
|
+
if (!reader) {
|
|
100
|
+
const { bytes, tooLarge } = await readCappedBytes(response, maxBytes);
|
|
101
|
+
if (tooLarge) return {
|
|
102
|
+
text: "",
|
|
103
|
+
tooLarge: true,
|
|
104
|
+
truncated: false
|
|
105
|
+
};
|
|
106
|
+
return capToLines(new TextDecoder().decode(bytes), maxLines);
|
|
107
|
+
}
|
|
108
|
+
const decoder = new TextDecoder();
|
|
109
|
+
let text = "";
|
|
110
|
+
let lines = 0;
|
|
111
|
+
let total = 0;
|
|
112
|
+
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
|
|
113
|
+
total += chunk.value.byteLength;
|
|
114
|
+
if (total > maxBytes) {
|
|
115
|
+
await reader.cancel();
|
|
116
|
+
return capAtBytes(text, lines, maxLines);
|
|
117
|
+
}
|
|
118
|
+
const piece = decoder.decode(chunk.value, { stream: true });
|
|
119
|
+
text += piece;
|
|
120
|
+
if (probeLines !== void 0) {
|
|
121
|
+
lines += countNewlines(piece);
|
|
122
|
+
if (lines >= probeLines) {
|
|
123
|
+
await reader.cancel();
|
|
124
|
+
return capToLines(text, maxLines);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
text += decoder.decode();
|
|
129
|
+
return capToLines(text, maxLines);
|
|
130
|
+
}
|
|
131
|
+
/** Keep the last `maxLines` lines, ignoring a single trailing newline so a file
|
|
132
|
+
* ending in one doesn't spend a line on the empty remainder. */
|
|
133
|
+
function lastLines(text, maxLines) {
|
|
134
|
+
const end = text.endsWith("\n") ? text.length - 1 : text.length;
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (let i = end - 1; i >= 0; i--) if (text[i] === "\n") {
|
|
137
|
+
count++;
|
|
138
|
+
if (count === maxLines) return {
|
|
139
|
+
text: text.slice(i + 1),
|
|
140
|
+
truncated: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
text,
|
|
145
|
+
truncated: false
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Range header for reading the last `maxBytes` of an object. A closed range is a
|
|
149
|
+
* CORS-safelisted header value, so it travels without a preflight that a bucket's
|
|
150
|
+
* CORS rules may not answer; the suffix form is not safelisted, and is only reached
|
|
151
|
+
* for workspace files, which are same-origin and list no size. Closing the range at
|
|
152
|
+
* the listed size also bounds the window: left open, an object that grew since the
|
|
153
|
+
* listing would serve more than `maxBytes` and the read would refuse it. */
|
|
154
|
+
function tailRangeFor(knownSize, maxBytes) {
|
|
155
|
+
if (typeof knownSize !== "number") return `bytes=-${maxBytes}`;
|
|
156
|
+
const start = knownSize - maxBytes;
|
|
157
|
+
return start > 0 ? `bytes=${start}-${knownSize - 1}` : null;
|
|
158
|
+
}
|
|
159
|
+
/** What a ranged response actually delivered: whether its first line arrives cut in
|
|
160
|
+
* half, or `null` when the body can't be the object's end at all. */
|
|
161
|
+
function tailWindow(response, maxBytes) {
|
|
162
|
+
if (response.status === 206) {
|
|
163
|
+
const range = response.headers.get("content-range");
|
|
164
|
+
return { startsMidObject: range === null || !range.startsWith("bytes 0-") };
|
|
165
|
+
}
|
|
166
|
+
const length = response.headers.get("content-length");
|
|
167
|
+
if (length === null) return null;
|
|
168
|
+
const size = Number(length);
|
|
169
|
+
return Number.isFinite(size) && size <= maxBytes ? { startsMidObject: false } : null;
|
|
170
|
+
}
|
|
171
|
+
/** Read a range covering the end of an object as text. `startsMidObject` says the
|
|
172
|
+
* range began at an arbitrary byte, so the first line arrives cut in half. */
|
|
173
|
+
async function readTailText(response, maxBytes, maxLines, startsMidObject) {
|
|
174
|
+
const { bytes, tooLarge } = await readCappedBytes(response, maxBytes);
|
|
175
|
+
if (tooLarge) return {
|
|
176
|
+
text: "",
|
|
177
|
+
tooLarge: true,
|
|
178
|
+
truncated: false
|
|
179
|
+
};
|
|
180
|
+
let text = new TextDecoder().decode(bytes);
|
|
181
|
+
if (startsMidObject) {
|
|
182
|
+
const firstBreak = text.indexOf("\n");
|
|
183
|
+
if (firstBreak !== -1) text = text.slice(firstBreak + 1);
|
|
184
|
+
}
|
|
185
|
+
const limited = maxLines === void 0 ? {
|
|
186
|
+
text,
|
|
187
|
+
truncated: false
|
|
188
|
+
} : lastLines(text, maxLines);
|
|
189
|
+
return {
|
|
190
|
+
text: limited.text,
|
|
191
|
+
tooLarge: false,
|
|
192
|
+
truncated: startsMidObject || limited.truncated
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
export { countLines, lastLines, readCappedBytes, readCappedText, readTailText, tailRangeFor, tailWindow };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CorsError } from "../lib/errors.js";
|
|
2
|
+
import { fetchPreviewObject, loadPreviewContent, peekPreviewContent } from "./previewCache.js";
|
|
3
|
+
import { readCappedBytes } from "./readCappedBytes.js";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
//#region src/file-explorer/FilePreview/useObjectBlobUrl.ts
|
|
6
|
+
function toState(result) {
|
|
7
|
+
return {
|
|
8
|
+
blobUrl: result.blobUrl,
|
|
9
|
+
loading: false,
|
|
10
|
+
error: null,
|
|
11
|
+
tooLarge: result.tooLarge,
|
|
12
|
+
corsError: false
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function disposeBlob(result) {
|
|
16
|
+
if (result.blobUrl) URL.revokeObjectURL(result.blobUrl);
|
|
17
|
+
}
|
|
18
|
+
/** Fetch an object into a blob URL with a forced MIME type so e.g. PDFs render inline even when served as octet-stream; cached per `cacheKey` so repeated mounts share one download and one blob URL, revoked when the cache drops it. */
|
|
19
|
+
function useObjectBlobUrl(url, { enabled, maxBytes, knownSize, mimeType, cacheKey }) {
|
|
20
|
+
const [state, setState] = useState({
|
|
21
|
+
blobUrl: null,
|
|
22
|
+
loading: enabled,
|
|
23
|
+
error: null,
|
|
24
|
+
tooLarge: false,
|
|
25
|
+
corsError: false
|
|
26
|
+
});
|
|
27
|
+
const tooLarge = typeof knownSize === "number" && knownSize > maxBytes;
|
|
28
|
+
const key = enabled ? cacheKey : null;
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!enabled || !url || !key) return;
|
|
31
|
+
if (tooLarge) {
|
|
32
|
+
setState({
|
|
33
|
+
blobUrl: null,
|
|
34
|
+
loading: false,
|
|
35
|
+
error: null,
|
|
36
|
+
tooLarge: true,
|
|
37
|
+
corsError: false
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
const record = loadPreviewContent(key, async (signal) => {
|
|
43
|
+
const response = await fetchPreviewObject(key, url, signal);
|
|
44
|
+
const { bytes, tooLarge: exceeded } = await readCappedBytes(response, maxBytes);
|
|
45
|
+
if (exceeded) return {
|
|
46
|
+
blobUrl: null,
|
|
47
|
+
tooLarge: true
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
blobUrl: URL.createObjectURL(new Blob([bytes], { type: mimeType })),
|
|
51
|
+
tooLarge: false
|
|
52
|
+
};
|
|
53
|
+
}, disposeBlob);
|
|
54
|
+
setState(record.value ? toState(record.value) : {
|
|
55
|
+
blobUrl: null,
|
|
56
|
+
loading: true,
|
|
57
|
+
error: null,
|
|
58
|
+
tooLarge: false,
|
|
59
|
+
corsError: false
|
|
60
|
+
});
|
|
61
|
+
record.promise.then((result) => {
|
|
62
|
+
if (!cancelled) setState(toState(result));
|
|
63
|
+
}).catch((err) => {
|
|
64
|
+
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
65
|
+
if (!cancelled) setState({
|
|
66
|
+
blobUrl: null,
|
|
67
|
+
loading: false,
|
|
68
|
+
error: err instanceof Error ? err.message : "Failed to load file",
|
|
69
|
+
tooLarge: false,
|
|
70
|
+
corsError: err instanceof CorsError
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return () => {
|
|
74
|
+
cancelled = true;
|
|
75
|
+
};
|
|
76
|
+
}, [
|
|
77
|
+
url,
|
|
78
|
+
enabled,
|
|
79
|
+
key,
|
|
80
|
+
maxBytes,
|
|
81
|
+
mimeType,
|
|
82
|
+
tooLarge
|
|
83
|
+
]);
|
|
84
|
+
if (tooLarge) return {
|
|
85
|
+
blobUrl: null,
|
|
86
|
+
loading: false,
|
|
87
|
+
error: null,
|
|
88
|
+
tooLarge: true,
|
|
89
|
+
corsError: false
|
|
90
|
+
};
|
|
91
|
+
const cached = key ? peekPreviewContent(key) : void 0;
|
|
92
|
+
return cached ? toState(cached) : state;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { useObjectBlobUrl };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CorsError } from "../lib/errors.js";
|
|
2
|
+
import { fetchPreviewObject, loadPreviewContent, peekPreviewContent } from "./previewCache.js";
|
|
3
|
+
import { readCappedText, readTailText, tailRangeFor, tailWindow } from "./readCappedBytes.js";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
//#region src/file-explorer/FilePreview/useObjectText.ts
|
|
6
|
+
var IDLE = {
|
|
7
|
+
text: null,
|
|
8
|
+
loading: false,
|
|
9
|
+
error: null,
|
|
10
|
+
tooLarge: false,
|
|
11
|
+
truncated: false,
|
|
12
|
+
corsError: false
|
|
13
|
+
};
|
|
14
|
+
function toState(result) {
|
|
15
|
+
return {
|
|
16
|
+
...IDLE,
|
|
17
|
+
...result
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function readObjectText(signal, { url, cacheKey, maxBytes, maxLines, knownSize, tail }) {
|
|
21
|
+
const range = tail ? tailRangeFor(knownSize, maxBytes) : null;
|
|
22
|
+
const response = await fetchPreviewObject(cacheKey, url, signal, range ? { Range: range } : void 0);
|
|
23
|
+
let startsMidObject = false;
|
|
24
|
+
if (range) {
|
|
25
|
+
const window = tailWindow(response, maxBytes);
|
|
26
|
+
if (!window) throw new Error("range request was not honored");
|
|
27
|
+
startsMidObject = window.startsMidObject;
|
|
28
|
+
}
|
|
29
|
+
const result = tail ? await readTailText(response, maxBytes, maxLines, startsMidObject) : await readCappedText(response, maxBytes, maxLines);
|
|
30
|
+
return {
|
|
31
|
+
text: result.tooLarge ? null : result.text,
|
|
32
|
+
tooLarge: result.tooLarge,
|
|
33
|
+
truncated: result.truncated
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Fetch a text object from a presigned URL, cached per `cacheKey` so repeated mounts
|
|
37
|
+
* download it once. `maxLines` stops the read early and `maxBytes` cuts short what it
|
|
38
|
+
* hasn't. `tail` reads the end of the object instead of the start, and caches
|
|
39
|
+
* separately because the same object yields different text. */
|
|
40
|
+
function useObjectText(url, { enabled, maxBytes, maxLines, knownSize, cacheKey, tail = false }) {
|
|
41
|
+
const tooLarge = maxLines === void 0 && !tail && typeof knownSize === "number" && knownSize > maxBytes;
|
|
42
|
+
const key = enabled ? tail ? `${cacheKey}#tail` : cacheKey : null;
|
|
43
|
+
const [state, setState] = useState({
|
|
44
|
+
...IDLE,
|
|
45
|
+
loading: enabled
|
|
46
|
+
});
|
|
47
|
+
const [stateKey, setStateKey] = useState(key);
|
|
48
|
+
if (stateKey !== key) {
|
|
49
|
+
setStateKey(key);
|
|
50
|
+
setState({
|
|
51
|
+
...IDLE,
|
|
52
|
+
loading: key !== null
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!enabled || !url || !key) return;
|
|
57
|
+
if (tooLarge) {
|
|
58
|
+
setState({
|
|
59
|
+
...IDLE,
|
|
60
|
+
tooLarge: true
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let cancelled = false;
|
|
65
|
+
const record = loadPreviewContent(key, (signal) => readObjectText(signal, {
|
|
66
|
+
url,
|
|
67
|
+
cacheKey,
|
|
68
|
+
maxBytes,
|
|
69
|
+
maxLines,
|
|
70
|
+
knownSize,
|
|
71
|
+
tail
|
|
72
|
+
}));
|
|
73
|
+
setState(record.value ? toState(record.value) : {
|
|
74
|
+
...IDLE,
|
|
75
|
+
loading: true
|
|
76
|
+
});
|
|
77
|
+
record.promise.then((result) => {
|
|
78
|
+
if (!cancelled) setState(toState(result));
|
|
79
|
+
}).catch((err) => {
|
|
80
|
+
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
81
|
+
if (!cancelled) setState({
|
|
82
|
+
...IDLE,
|
|
83
|
+
error: err instanceof Error ? err.message : "Failed to load file",
|
|
84
|
+
corsError: err instanceof CorsError
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
cancelled = true;
|
|
89
|
+
};
|
|
90
|
+
}, [
|
|
91
|
+
url,
|
|
92
|
+
enabled,
|
|
93
|
+
key,
|
|
94
|
+
maxBytes,
|
|
95
|
+
maxLines,
|
|
96
|
+
tooLarge,
|
|
97
|
+
tail,
|
|
98
|
+
cacheKey,
|
|
99
|
+
knownSize
|
|
100
|
+
]);
|
|
101
|
+
if (tooLarge) return {
|
|
102
|
+
...IDLE,
|
|
103
|
+
tooLarge: true
|
|
104
|
+
};
|
|
105
|
+
const cached = key ? peekPreviewContent(key) : void 0;
|
|
106
|
+
return cached ? toState(cached) : state;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { useObjectText };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { loadPreviewUrl, peekPreviewUrl } from "./previewCache.js";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
//#region src/file-explorer/FilePreview/usePresignedUrl.ts
|
|
4
|
+
function toState([url, error], linkErrorMessage) {
|
|
5
|
+
if (error || !url) return {
|
|
6
|
+
url: null,
|
|
7
|
+
loading: false,
|
|
8
|
+
error: error?.message ?? linkErrorMessage
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
url,
|
|
12
|
+
loading: false,
|
|
13
|
+
error: null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** 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. */
|
|
17
|
+
function usePresignedUrl(node, getPresignedUrl, { enabled, linkErrorMessage }) {
|
|
18
|
+
const path = enabled ? node?.path ?? null : null;
|
|
19
|
+
const [state, setState] = useState({
|
|
20
|
+
url: null,
|
|
21
|
+
loading: false,
|
|
22
|
+
error: null
|
|
23
|
+
});
|
|
24
|
+
const [statePath, setStatePath] = useState(path);
|
|
25
|
+
if (statePath !== path) {
|
|
26
|
+
setStatePath(path);
|
|
27
|
+
setState({
|
|
28
|
+
url: null,
|
|
29
|
+
loading: path !== null,
|
|
30
|
+
error: null
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const getUrlRef = useRef(getPresignedUrl);
|
|
34
|
+
getUrlRef.current = getPresignedUrl;
|
|
35
|
+
const linkErrorRef = useRef(linkErrorMessage);
|
|
36
|
+
linkErrorRef.current = linkErrorMessage;
|
|
37
|
+
const nodeRef = useRef(node);
|
|
38
|
+
nodeRef.current = node;
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const node = nodeRef.current;
|
|
41
|
+
if (!path || !node) {
|
|
42
|
+
setState({
|
|
43
|
+
url: null,
|
|
44
|
+
loading: false,
|
|
45
|
+
error: null
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let cancelled = false;
|
|
50
|
+
const record = loadPreviewUrl(path, () => getUrlRef.current(node));
|
|
51
|
+
setState(record.value ? toState(record.value, linkErrorRef.current) : {
|
|
52
|
+
url: null,
|
|
53
|
+
loading: true,
|
|
54
|
+
error: null
|
|
55
|
+
});
|
|
56
|
+
record.promise.then((result) => {
|
|
57
|
+
if (!cancelled) setState(toState(result, linkErrorRef.current));
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
if (!cancelled) setState({
|
|
60
|
+
url: null,
|
|
61
|
+
loading: false,
|
|
62
|
+
error: err instanceof Error ? err.message : linkErrorRef.current
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, [path]);
|
|
69
|
+
const cached = path ? peekPreviewUrl(path) : void 0;
|
|
70
|
+
return cached ? toState(cached, linkErrorRef.current) : state;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { usePresignedUrl };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type OpenMenu, type RowMenuItem } from '../list/index.js';
|
|
2
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import { type OpenMenu, type RowMenuItem } from '../list/index.js';
|
|
3
3
|
import type { TreeNode } from './lib/types.js';
|
|
4
4
|
export declare const ROW_HEIGHT_SHORT = 45;
|
|
5
5
|
export declare const ROW_HEIGHT_TALL = 52.25;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useStrings } from "../components/Provider.js";
|
|
2
|
+
import { EyeIcon } from "../icons.js";
|
|
3
|
+
import { TOOLTIP_ID } from "../components/Tooltip.js";
|
|
4
|
+
import { MoreButton, useRowMenuActive } from "../list/RowContextMenu.js";
|
|
5
|
+
import { Table } from "../components/Table.js";
|
|
6
|
+
import { RowSelectCheckbox } from "../list/ListTable.js";
|
|
7
|
+
import { formatFileSize } from "./lib/utils.js";
|
|
8
|
+
import cx from "classnames";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { DateTime } from "luxon";
|
|
11
|
+
var ROW_HEIGHT_TALL = 52.25;
|
|
12
|
+
function isTwoLineRow(node) {
|
|
13
|
+
return node.type !== "directory" && !!node.contentType;
|
|
14
|
+
}
|
|
15
|
+
/** Pure function of the node, so the virtualizer sizes rows without measuring. */
|
|
16
|
+
function fileRowHeight(node) {
|
|
17
|
+
return isTwoLineRow(node) ? ROW_HEIGHT_TALL : 45;
|
|
18
|
+
}
|
|
19
|
+
function isInteractiveTarget(e) {
|
|
20
|
+
return !!e.target.closest("a,button,input,label");
|
|
21
|
+
}
|
|
22
|
+
function lazyContextMenuProps(openMenu, getItems) {
|
|
23
|
+
return { onContextMenu: (e) => {
|
|
24
|
+
const items = getItems();
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
if (items.length > 0) e.stopPropagation();
|
|
27
|
+
openMenu(e.clientX, e.clientY, items);
|
|
28
|
+
} };
|
|
29
|
+
}
|
|
30
|
+
function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen }) {
|
|
31
|
+
const t = useStrings().fileExplorer;
|
|
32
|
+
const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
|
|
33
|
+
const handleRowClick = (e) => {
|
|
34
|
+
if (isInteractiveTarget(e)) return;
|
|
35
|
+
onOpen(node);
|
|
36
|
+
};
|
|
37
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
38
|
+
className: cx("group cursor-pointer", menuActive ? "theme-hover" : "hover:theme-hover"),
|
|
39
|
+
onClick: handleRowClick,
|
|
40
|
+
...lazyContextMenuProps(openRowMenu, getItems),
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
43
|
+
className: "py-2.5",
|
|
44
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
45
|
+
className: "flex min-w-0 items-center gap-3",
|
|
46
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
47
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full theme-muted-panel *:m-0",
|
|
48
|
+
children: icon
|
|
49
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
50
|
+
className: "truncate text-[13px] font-medium text-(--theme-app)",
|
|
51
|
+
children: node.displayName || node.name
|
|
52
|
+
})]
|
|
53
|
+
})
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
56
|
+
className: "py-2.5 text-xs uppercase tracking-wider text-(--theme-muted-text-color)",
|
|
57
|
+
children: t.preview.userType
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
60
|
+
className: "py-2.5 tabular-nums text-(--theme-muted-text-color)",
|
|
61
|
+
children: storageCount
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
64
|
+
className: "relative py-2.5",
|
|
65
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
66
|
+
className: "absolute inset-y-0 right-0 flex items-center pr-4",
|
|
67
|
+
children: /* @__PURE__ */ jsx(MoreButton, {
|
|
68
|
+
active: menuActive,
|
|
69
|
+
onOpen: (x, y) => openRowMenu(x, y, getItems())
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
|
|
77
|
+
const t = useStrings().fileExplorer;
|
|
78
|
+
const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
|
|
79
|
+
const isDir = node.type === "directory";
|
|
80
|
+
const modifiedDt = node.modified ? DateTime.fromISO(node.modified) : null;
|
|
81
|
+
const typeLabel = node.root ? node.storageType ?? node.type : node.type;
|
|
82
|
+
const handleRowClick = (e) => {
|
|
83
|
+
if (isInteractiveTarget(e)) return;
|
|
84
|
+
onOpen(node);
|
|
85
|
+
};
|
|
86
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
87
|
+
id: rowId,
|
|
88
|
+
"aria-rowindex": rowIndex,
|
|
89
|
+
"aria-selected": checked,
|
|
90
|
+
className: cx("group", checked ? "bg-blue-500/5" : menuActive ? "theme-hover" : "hover:theme-hover", active && "group-focus-visible/grid:ring-1 group-focus-visible/grid:ring-inset group-focus-visible/grid:ring-(--theme-link)", "cursor-pointer"),
|
|
91
|
+
onClick: handleRowClick,
|
|
92
|
+
style: { height: fileRowHeight(node) },
|
|
93
|
+
...lazyContextMenuProps(openRowMenu, getItems),
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
96
|
+
className: "py-2.5",
|
|
97
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
98
|
+
className: "flex min-w-0 items-center gap-2.5",
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsx(RowSelectCheckbox, {
|
|
101
|
+
checked,
|
|
102
|
+
label: t.preview.select(node.name),
|
|
103
|
+
onToggle: () => onToggleCheck(node, !checked)
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ jsx("span", {
|
|
106
|
+
className: "flex h-5 w-5 shrink-0 items-center justify-center *:m-0",
|
|
107
|
+
children: icon
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ jsxs("div", {
|
|
110
|
+
className: "min-w-0",
|
|
111
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
112
|
+
className: "truncate text-[13px] font-medium leading-tight text-(--theme-app)",
|
|
113
|
+
children: node.displayName || node.name
|
|
114
|
+
}), !isDir && node.contentType && /* @__PURE__ */ jsx("div", {
|
|
115
|
+
className: "truncate text-xs leading-tight text-(--theme-muted-text-color)",
|
|
116
|
+
children: node.contentType
|
|
117
|
+
})]
|
|
118
|
+
})
|
|
119
|
+
]
|
|
120
|
+
})
|
|
121
|
+
}),
|
|
122
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
123
|
+
className: "truncate py-2.5 text-sm tabular-nums text-(--theme-muted-text-color)",
|
|
124
|
+
children: typeof node.size === "number" ? formatFileSize(node.size) : "—"
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
127
|
+
className: "truncate py-2.5 text-xs uppercase tracking-wider text-(--theme-muted-text-color)",
|
|
128
|
+
children: typeLabel
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
131
|
+
className: "truncate py-2.5 text-sm text-(--theme-muted-text-color)",
|
|
132
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
133
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
134
|
+
"data-tooltip-content": node.modified ?? "",
|
|
135
|
+
"data-tooltip-place": "top",
|
|
136
|
+
children: !isDir && modifiedDt?.isValid ? modifiedDt.toRelative() : "—"
|
|
137
|
+
})
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ jsx(Table.Item, {
|
|
140
|
+
className: "relative py-2.5",
|
|
141
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
142
|
+
className: "absolute inset-y-0 right-0 flex items-center gap-0.5 pr-4",
|
|
143
|
+
children: [!isDir && canPreview && /* @__PURE__ */ jsx("button", {
|
|
144
|
+
type: "button",
|
|
145
|
+
"aria-label": t.preview.preview,
|
|
146
|
+
"data-tooltip-id": "global-tooltip",
|
|
147
|
+
"data-tooltip-content": t.preview.preview,
|
|
148
|
+
"data-tooltip-place": "bottom",
|
|
149
|
+
onClick: (e) => {
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
onPreview(node);
|
|
152
|
+
},
|
|
153
|
+
className: cx("rounded p-1 text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) [&>svg]:h-4 [&>svg]:w-4", menuActive ? "opacity-100" : "opacity-0 group-hover:opacity-100 focus-visible:opacity-100"),
|
|
154
|
+
children: /* @__PURE__ */ jsx(EyeIcon, {})
|
|
155
|
+
}), /* @__PURE__ */ jsx(MoreButton, {
|
|
156
|
+
active: menuActive,
|
|
157
|
+
onOpen: (x, y) => openRowMenu(x, y, getItems())
|
|
158
|
+
})]
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { FileRow, ROW_HEIGHT_TALL, UserRow, fileRowHeight, isTwoLineRow };
|