@parallelworks/ui 0.12.0 → 0.12.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.
@@ -581,12 +581,13 @@ function CreateModalSelectPanel({ open, options, label, value, collapsedLabel, o
581
581
  * icons and group headers (e.g. Clusters vs Kubernetes). */
582
582
  function CreateModalSelect({ label, icon, value, options, onChange, disabled = false, disabledHint, tooltip, collapsedLabel }) {
583
583
  const selected = options.find((o) => o.value === value);
584
+ const selectedLabel = selected?.label ?? (value || void 0);
584
585
  const pillContent = /* @__PURE__ */ jsxs(Fragment, { children: [
585
586
  selected?.icon ?? icon,
586
- selected ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
587
+ selectedLabel ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
587
588
  className: "font-normal text-(--theme-muted-text-color)",
588
589
  children: label
589
- }), selected.label] }) : label,
590
+ }), selectedLabel] }) : label,
590
591
  /* @__PURE__ */ jsx(ChevronDownIcon, { className: "w-3 h-3 opacity-60" })
591
592
  ] });
592
593
  if (disabled) return /* @__PURE__ */ jsx("button", {
@@ -602,7 +603,7 @@ function CreateModalSelect({ label, icon, value, options, onChange, disabled = f
602
603
  });
603
604
  return /* @__PURE__ */ jsx(Menu, { children: ({ open }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MenuButton, {
604
605
  "aria-label": label,
605
- className: cx("inline-flex h-7 rounded-md border border-(--theme-border) text-xs font-medium transition-colors hover:bg-(--theme-muted-panel-bg)", selected ? "text-(--theme-app)" : "text-(--theme-muted-text-color)"),
606
+ className: cx("inline-flex h-7 rounded-md border border-(--theme-border) text-xs font-medium transition-colors hover:bg-(--theme-muted-panel-bg)", selectedLabel ? "text-(--theme-app)" : "text-(--theme-muted-text-color)"),
606
607
  children: /* @__PURE__ */ jsx(PillTooltip, {
607
608
  content: tooltip,
608
609
  className: "px-2",
@@ -7,4 +7,4 @@ export { LIST_PAGE_SIZE } from './lib/paging.js';
7
7
  export type { TreeNode, TStorage, TStorageObject } from './lib/types.js';
8
8
  export { CorsError } from './lib/errors.js';
9
9
  export { chunk, mapWithConcurrency, paginate } from './lib/batch.js';
10
- export { collectObjectKeysToDelete, getObjectKeyFromNode, isClientValid, removeLeadingAndTrailingSlashes, } from './lib/utils.js';
10
+ export { attachmentDisposition, collectObjectKeysToDelete, getObjectKeyFromNode, isClientValid, removeLeadingAndTrailingSlashes, } from './lib/utils.js';
@@ -1,5 +1,5 @@
1
1
  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 ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-g6c-9-6N.js";
2
+ import { c as ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-DLCC8t_x.js";
3
3
  import { a as useProvisionCorsRules, c as useStrings, i as useNotify, s as useSlots } from "../chunks/Provider-CfFV-vc5.js";
4
4
  import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
5
5
  import { t as StatusBadge } from "../chunks/StatusBadge-P6C2tv7Z.js";
@@ -239,6 +239,9 @@ function calculateUploadNodesTotalSize(uploadNodes) {
239
239
  function getObjectKeyFromNode(node) {
240
240
  return node.relativePath || node.path.split("/").filter(Boolean).slice(1).join("/");
241
241
  }
242
+ function attachmentDisposition(filename) {
243
+ return `attachment; filename="${filename.replace(/[^\x20-\x7e]|["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(filename).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
244
+ }
242
245
  function getDirectoryPrefixFromNode(node) {
243
246
  const key = getObjectKeyFromNode(node);
244
247
  if (!key) throw new Error(`Cannot resolve an object prefix for ${node.path}`);
@@ -4641,7 +4644,7 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
4641
4644
  return;
4642
4645
  }
4643
4646
  for (const [index, node] of filesToDownload.entries()) {
4644
- const input = provider.getFileInput(node.storageName ?? "", node);
4647
+ const input = provider.getFileInput(node.storageName ?? "", node, void 0, node.name);
4645
4648
  const [presignedUrl, error] = await client.getFile(input);
4646
4649
  if (error || !presignedUrl) {
4647
4650
  notify.error(error?.message ?? t.messages.downloadLinkFailed);
@@ -4679,12 +4682,12 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
4679
4682
  document.body.removeChild(a);
4680
4683
  }, 300);
4681
4684
  };
4682
- const getPresignedUrl = async (node) => {
4685
+ const getPresignedUrl = async (node, downloadFilename) => {
4683
4686
  const factory = await getStorageProviderAndClient(node.storageId);
4684
4687
  if (!factory) return [null, /* @__PURE__ */ new Error("Failed to connect to storage provider.")];
4685
4688
  const { provider, client } = factory;
4686
4689
  if (!provider?.getFileInput) return [null, /* @__PURE__ */ new Error("Preview is not supported for this storage.")];
4687
- const input = provider.getFileInput(node.storageName ?? "", node, PREVIEW_URL_EXPIRES_IN);
4690
+ const input = provider.getFileInput(node.storageName ?? "", node, PREVIEW_URL_EXPIRES_IN, downloadFilename);
4688
4691
  return client.getFile(input);
4689
4692
  };
4690
4693
  const previewSiblings = useMemo(() => previewNode ? getNodeChildren(parentToChildrenMap, getParentPath(previewNode.path)).filter((n) => n.type === "file") : [], [previewNode, parentToChildrenMap]);
@@ -4723,7 +4726,7 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
4723
4726
  }
4724
4727
  };
4725
4728
  const downloadNode = async (node) => {
4726
- const [presignedUrl, error] = await getPresignedUrl(node);
4729
+ const [presignedUrl, error] = await getPresignedUrl(node, node.name);
4727
4730
  if (error || !presignedUrl) {
4728
4731
  notify.error(error?.message ?? t.messages.downloadLinkFailed);
4729
4732
  return;
@@ -5641,4 +5644,4 @@ async function mapWithConcurrency(items, limit, worker) {
5641
5644
  return results;
5642
5645
  }
5643
5646
  //#endregion
5644
- export { CorsError, FileExplorer, FileExplorerProvider, LIST_PAGE_SIZE, NoDataAvailablePreview, SkeletonPreview, chunk, collectObjectKeysToDelete, getObjectKeyFromNode, isClientValid, mapWithConcurrency, paginate, removeLeadingAndTrailingSlashes, useFileExplorer };
5647
+ export { CorsError, FileExplorer, FileExplorerProvider, LIST_PAGE_SIZE, NoDataAvailablePreview, SkeletonPreview, attachmentDisposition, chunk, collectObjectKeysToDelete, getObjectKeyFromNode, isClientValid, mapWithConcurrency, paginate, removeLeadingAndTrailingSlashes, useFileExplorer };
@@ -26,7 +26,9 @@ export interface IFileExplorerProvider {
26
26
  * the same bivariance reason as the two above. */
27
27
  nextPageCursor?(data: unknown): string | undefined;
28
28
  listDirectoryInput?: ((storage: TStorage, dir_path?: string, page?: TListPageRequest) => unknown) | null;
29
- getFileInput?: ((storageName: string, node: TreeNode, presignedUrlExpiresIn?: number) => unknown) | null;
29
+ /** `downloadFilename` asks for a URL that saves under that name. Omit it for a
30
+ * link the user keeps, or for a read the app parses itself. */
31
+ getFileInput?: ((storageName: string, node: TreeNode, presignedUrlExpiresIn?: number, downloadFilename?: string) => unknown) | null;
30
32
  deleteFilesInput?: ((storageName: string, nodes: TreeNode[]) => unknown) | null;
31
33
  uploadFileInput?: ((storageName: string, objectPath: string, file: File | Blob) => unknown) | null;
32
34
  }
@@ -20,6 +20,7 @@ export declare function getUploadNodesFromDataTransferItems(items: DataTransferI
20
20
  }>;
21
21
  export declare function calculateUploadNodesTotalSize(uploadNodes: UploadNode[]): number;
22
22
  export declare function getObjectKeyFromNode(node: TreeNode): string;
23
+ export declare function attachmentDisposition(filename: string): string;
23
24
  export declare function getDirectoryPrefixFromNode(node: TreeNode): string;
24
25
  export declare function pruneNodesInsideSelectedDirectories(nodes: TreeNode[]): TreeNode[];
25
26
  export declare function collectObjectKeysToDelete(nodes: TreeNode[], listKeysUnderPrefix: (prefix: string) => Promise<string[]>): Promise<string[]>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_PRESET, THEME_PRESETS, THEME_TOKENS, applyTheme, contrastRatio, deriveTheme, isDarkColor } from "./theme/index.js";
2
2
  import { AlertIcon, AngleRightIcon, EyeIcon, EyeOffIcon, FilterIcon, LoaderIcon, SortDownIcon, SortUnsortedIcon, SortUpIcon } from "./icons.js";
3
- import { a as CreateModalSelect, c as ConfirmModal, d as primaryButtonClasses, f as Markdown, h as Callout, i as CreateModalField, l as dangerButtonClasses, m as CopyToClipboard, n as CreateModal, o as CreateModalSwitch, p as CopyCodeBlock, r as CreateModalAutocompleteField, s as CreateModalTextarea, t as CREATE_MODAL_SELECT_FILTER_THRESHOLD, u as ghostButtonClasses } from "./chunks/CreateModal-g6c-9-6N.js";
3
+ import { a as CreateModalSelect, c as ConfirmModal, d as primaryButtonClasses, f as Markdown, h as Callout, i as CreateModalField, l as dangerButtonClasses, m as CopyToClipboard, n as CreateModal, o as CreateModalSwitch, p as CopyCodeBlock, r as CreateModalAutocompleteField, s as CreateModalTextarea, t as CREATE_MODAL_SELECT_FILTER_THRESHOLD, u as ghostButtonClasses } from "./chunks/CreateModal-DLCC8t_x.js";
4
4
  import { a as useProvisionCorsRules, c as useStrings, i as useNotify, l as useWorkflowJsonResolver, n as useLink, o as useRunFile, r as useNavigation, s as useSlots, t as UIProvider } from "./chunks/Provider-CfFV-vc5.js";
5
5
  import { i as formatTooltipContent, n as TOOLTIP_ID, r as TooltipInfo, t as GlobalTooltip } from "./chunks/Tooltip-WgbiC_nd.js";
6
6
  import { c as isOption, i as filterOption, l as toggleSetMember, n as StatusDot, o as flattenOptions, r as DropdownCaret, s as isCategory, t as StatusBadge } from "./chunks/StatusBadge-P6C2tv7Z.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parallelworks/ui",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Shared UI foundation for Parallel Works packages: theme contract, icons, and core primitives",
5
5
  "license": "MIT",
6
6
  "author": "Parallel Works <support@parallelworks.com>",