diff --git a/apps/console/src/hooks/graph/EvidenceGraph.ts b/apps/console/src/hooks/graph/EvidenceGraph.ts new file mode 100644 index 000000000..a7bfee726 --- /dev/null +++ b/apps/console/src/hooks/graph/EvidenceGraph.ts @@ -0,0 +1,14 @@ +import { graphql } from "relay-runtime"; + +export const evidenceFileQuery = graphql` + query EvidenceGraphFileQuery($evidenceId: ID!) { + node(id: $evidenceId) { + ... on Evidence { + id + mimeType + filename + fileUrl + } + } + } +`; diff --git a/apps/console/src/hooks/graph/__generated__/EvidenceGraphFileQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/EvidenceGraphFileQuery.graphql.ts new file mode 100644 index 000000000..efbf0c457 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/EvidenceGraphFileQuery.graphql.ts @@ -0,0 +1,154 @@ +/** + * @generated SignedSource<<0c4d6a4bd477e15ae3c627cf338a99dd>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type EvidenceGraphFileQuery$variables = { + evidenceId: string; +}; +export type EvidenceGraphFileQuery$data = { + readonly node: { + readonly fileUrl?: string | null | undefined; + readonly filename?: string; + readonly id?: string; + readonly mimeType?: string; + }; +}; +export type EvidenceGraphFileQuery = { + response: EvidenceGraphFileQuery$data; + variables: EvidenceGraphFileQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "evidenceId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "evidenceId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "mimeType", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "filename", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fileUrl", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "EvidenceGraphFileQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/) + ], + "type": "Evidence", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "EvidenceGraphFileQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/) + ], + "type": "Evidence", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "db46f9bf993514c7bc89845e6d6459f8", + "id": null, + "metadata": {}, + "name": "EvidenceGraphFileQuery", + "operationKind": "query", + "text": "query EvidenceGraphFileQuery(\n $evidenceId: ID!\n) {\n node(id: $evidenceId) {\n __typename\n ... on Evidence {\n id\n mimeType\n filename\n fileUrl\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "3c9aa6ff338d2380a7a03b8ea61e83e0"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/MeasureGraphNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/MeasureGraphNodeQuery.graphql.ts index 94e113d22..94920b3e0 100644 --- a/apps/console/src/hooks/graph/__generated__/MeasureGraphNodeQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/MeasureGraphNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<286a7d808d7a9c793cd9e9b6830cb40b>> + * @generated SignedSource<<074d65be2a59b6b8284ff6be0f75707d>> * @lightSyntaxTransform * @nogrep */ @@ -598,13 +598,6 @@ return { "name": "filename", "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fileUrl", - "storageKey": null - }, { "alias": null, "args": null, @@ -667,12 +660,12 @@ return { ] }, "params": { - "cacheID": "c36bf27c7f4d6b3ef4a3e063e4bfccea", + "cacheID": "6146a6fada0328c7b69652646212be05", "id": null, "metadata": {}, "name": "MeasureGraphNodeQuery", "operationKind": "query", - "text": "query MeasureGraphNodeQuery(\n $measureId: ID!\n) {\n node(id: $measureId) {\n __typename\n ... on Measure {\n id\n name\n description\n state\n category\n evidencesInfos: evidences(first: 0) {\n totalCount\n }\n risksInfos: risks(first: 0) {\n totalCount\n }\n tasksInfos: tasks(first: 0) {\n totalCount\n }\n controlsInfos: controls(first: 0) {\n totalCount\n }\n ...MeasureRisksTabFragment\n ...MeasureTasksTabFragment\n ...MeasureControlsTabFragment\n ...MeasureFormDialogMeasureFragment\n ...MeasureEvidencesTabFragment\n }\n id\n }\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n\nfragment LinkedRisksCardFragment on Risk {\n id\n name\n inherentRiskScore\n residualRiskScore\n}\n\nfragment MeasureControlsTabFragment on Measure {\n id\n controls(first: 20) {\n edges {\n node {\n id\n ...LinkedControlsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment on Measure {\n id\n evidences(first: 50) {\n edges {\n node {\n id\n filename\n fileUrl\n mimeType\n ...MeasureEvidencesTabFragment_evidence\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n fileUrl\n mimeType\n}\n\nfragment MeasureFormDialogMeasureFragment on Measure {\n id\n description\n name\n category\n state\n}\n\nfragment MeasureRisksTabFragment on Measure {\n id\n risks(first: 100) {\n edges {\n node {\n id\n ...LinkedRisksCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment MeasureTasksTabFragment on Measure {\n tasks(first: 100) {\n edges {\n node {\n id\n name\n state\n description\n ...TaskFormDialogFragment\n assignedTo {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TaskFormDialogFragment on Task {\n id\n description\n name\n state\n timeEstimate\n deadline\n assignedTo {\n id\n }\n measure {\n id\n }\n}\n" + "text": "query MeasureGraphNodeQuery(\n $measureId: ID!\n) {\n node(id: $measureId) {\n __typename\n ... on Measure {\n id\n name\n description\n state\n category\n evidencesInfos: evidences(first: 0) {\n totalCount\n }\n risksInfos: risks(first: 0) {\n totalCount\n }\n tasksInfos: tasks(first: 0) {\n totalCount\n }\n controlsInfos: controls(first: 0) {\n totalCount\n }\n ...MeasureRisksTabFragment\n ...MeasureTasksTabFragment\n ...MeasureControlsTabFragment\n ...MeasureFormDialogMeasureFragment\n ...MeasureEvidencesTabFragment\n }\n id\n }\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n\nfragment LinkedRisksCardFragment on Risk {\n id\n name\n inherentRiskScore\n residualRiskScore\n}\n\nfragment MeasureControlsTabFragment on Measure {\n id\n controls(first: 20) {\n edges {\n node {\n id\n ...LinkedControlsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment on Measure {\n id\n evidences(first: 50) {\n edges {\n node {\n id\n filename\n mimeType\n ...MeasureEvidencesTabFragment_evidence\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n mimeType\n}\n\nfragment MeasureFormDialogMeasureFragment on Measure {\n id\n description\n name\n category\n state\n}\n\nfragment MeasureRisksTabFragment on Measure {\n id\n risks(first: 100) {\n edges {\n node {\n id\n ...LinkedRisksCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment MeasureTasksTabFragment on Measure {\n tasks(first: 100) {\n edges {\n node {\n id\n name\n state\n description\n ...TaskFormDialogFragment\n assignedTo {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TaskFormDialogFragment on Task {\n id\n description\n name\n state\n timeEstimate\n deadline\n assignedTo {\n id\n }\n measure {\n id\n }\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/measures/dialog/EvidenceDownloadDialog.tsx b/apps/console/src/pages/organizations/measures/dialog/EvidenceDownloadDialog.tsx new file mode 100644 index 000000000..c935fda8f --- /dev/null +++ b/apps/console/src/pages/organizations/measures/dialog/EvidenceDownloadDialog.tsx @@ -0,0 +1,58 @@ +import { Breadcrumb, Dialog, DialogContent, Spinner } from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { Suspense, useEffect } from "react"; +import { useLazyLoadQuery } from "react-relay"; +import type { EvidenceGraphFileQuery } from "/hooks/graph/__generated__/EvidenceGraphFileQuery.graphql"; +import { evidenceFileQuery } from "/hooks/graph/EvidenceGraph"; +import { downloadFile } from "@probo/helpers"; + +type Props = { + evidenceId: string; + onClose: () => void; +}; +export function EvidenceDownloadDialog({ evidenceId, onClose }: Props) { + const { __ } = useTranslate(); + + return ( + + } + > + + + + {__("Generating download link")}... + + } + > + + + + + ); +} + +/** + * Force the download of an evidence file + */ +function DownloadLink({ evidenceId, onClose }: Props) { + const data = useLazyLoadQuery(evidenceFileQuery, { + evidenceId, + }); + const evidence = data.node; + + useEffect(() => { + downloadFile(evidence.fileUrl, evidence.filename ?? "evidence"); + onClose(); + }, [evidence]); + + return null; +} diff --git a/apps/console/src/pages/organizations/measures/dialog/EvidencePreviewDialog.tsx b/apps/console/src/pages/organizations/measures/dialog/EvidencePreviewDialog.tsx index efbec71d7..19297ff6c 100644 --- a/apps/console/src/pages/organizations/measures/dialog/EvidencePreviewDialog.tsx +++ b/apps/console/src/pages/organizations/measures/dialog/EvidencePreviewDialog.tsx @@ -10,18 +10,22 @@ import { useToast, } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; -import { useEffect } from "react"; +import { Suspense, useEffect } from "react"; +import { useLazyLoadQuery } from "react-relay"; +import { evidenceFileQuery } from "/hooks/graph/EvidenceGraph.ts"; +import type { EvidenceGraphFileQuery } from "/hooks/graph/__generated__/EvidenceGraphFileQuery.graphql.ts"; type Props = { - evidence: { - filename: string; - fileUrl?: string | null; - mimeType: string; - }; + evidenceId: string; + filename: string; onClose: () => void; }; -export function EvidencePreviewDialog({ evidence, onClose }: Props) { +export function EvidencePreviewDialog({ + evidenceId, + onClose, + filename, +}: Props) { const { __ } = useTranslate(); const ref = useDialogRef(); return ( @@ -29,17 +33,17 @@ export function EvidencePreviewDialog({ evidence, onClose }: Props) { ref={ref} defaultOpen title={ - + } onClose={onClose} > - ref.current?.close()} - /> + }> + ref.current?.close()} + /> + ); @@ -47,7 +51,7 @@ export function EvidencePreviewDialog({ evidence, onClose }: Props) { const fetchUrlFromUriFile = async ( fileUrl: string, - options?: { signal?: AbortSignal } + options?: { signal?: AbortSignal }, ): Promise => { const response = await fetch(fileUrl, options); const text = await response.text(); @@ -59,7 +63,15 @@ const fetchUrlFromUriFile = async ( return firstLine; }; -function EvidencePreviewContent({ evidence, onClose }: Props) { +function EvidencePreviewContent({ + evidenceId, + onClose, +}: Omit) { + const evidence = useLazyLoadQuery( + evidenceFileQuery, + { evidenceId: evidenceId }, + { fetchPolicy: "network-only" }, + ).node; const { __ } = useTranslate(); const { toast } = useToast(); const isUriFile = @@ -103,7 +115,7 @@ function EvidencePreviewContent({ evidence, onClose }: Props) { ); } - if (evidence.mimeType.startsWith("image/")) { + if (evidence.mimeType?.startsWith("image/")) { return ( > + * @generated SignedSource<<9297f85591b8ab4723a207f6db6a27f6>> * @lightSyntaxTransform * @nogrep */ @@ -173,13 +173,6 @@ return { "name": "createdAt", "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fileUrl", - "storageKey": null - }, { "alias": null, "args": null, @@ -215,12 +208,12 @@ return { ] }, "params": { - "cacheID": "7070386f52341969556753a27d42e226", + "cacheID": "5206b6576cacb45f5b42305bb1bb7df1", "id": null, "metadata": {}, "name": "CreateEvidenceDialogUploadMutation", "operationKind": "mutation", - "text": "mutation CreateEvidenceDialogUploadMutation(\n $input: UploadMeasureEvidenceInput!\n) {\n uploadMeasureEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n ...MeasureEvidencesTabFragment_evidence\n }\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n fileUrl\n mimeType\n}\n" + "text": "mutation CreateEvidenceDialogUploadMutation(\n $input: UploadMeasureEvidenceInput!\n) {\n uploadMeasureEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n ...MeasureEvidencesTabFragment_evidence\n }\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n mimeType\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/measures/tabs/MeasureEvidencesTab.tsx b/apps/console/src/pages/organizations/measures/tabs/MeasureEvidencesTab.tsx index bb918200c..966407e64 100644 --- a/apps/console/src/pages/organizations/measures/tabs/MeasureEvidencesTab.tsx +++ b/apps/console/src/pages/organizations/measures/tabs/MeasureEvidencesTab.tsx @@ -25,6 +25,8 @@ import { fileSize, fileType, promisifyMutation, sprintf } from "@probo/helpers"; import { EvidencePreviewDialog } from "../dialog/EvidencePreviewDialog"; import { useOrganizationId } from "/hooks/useOrganizationId"; import { CreateEvidenceDialog } from "../dialog/CreateEvidenceDialog"; +import { useState } from "react"; +import { EvidenceDownloadDialog } from "../dialog/EvidenceDownloadDialog"; export const evidencesFragment = graphql` fragment MeasureEvidencesTabFragment on Measure @@ -49,7 +51,6 @@ export const evidencesFragment = graphql` node { id filename - fileUrl mimeType ...MeasureEvidencesTabFragment_evidence } @@ -65,7 +66,6 @@ export const evidenceFragment = graphql` size type createdAt - fileUrl mimeType } `; @@ -137,7 +137,8 @@ export default function MeasureEvidencesTab() { `/organizations/${organizationId}/measures/${measure.id}/evidences`, ) } - evidence={evidence} + evidenceId={evidence.id} + filename={evidence.filename} /> )} { confirm( @@ -185,33 +187,39 @@ function EvidenceRow(props: { }; return ( - - {evidence.filename} - {fileType(__, evidence)} - {fileSize(__, evidence.size)} - {dateFormat(evidence.createdAt)} - - + + + ); } diff --git a/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment.graphql.ts b/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment.graphql.ts index dd90502cf..a2ac00cf4 100644 --- a/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment.graphql.ts +++ b/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<6c96b0b565302f1ec1601561087b30bd>> * @lightSyntaxTransform * @nogrep */ @@ -15,7 +15,6 @@ export type MeasureEvidencesTabFragment$data = { readonly __id: string; readonly edges: ReadonlyArray<{ readonly node: { - readonly fileUrl: string | null | undefined; readonly filename: string; readonly id: string; readonly mimeType: string; @@ -145,13 +144,6 @@ return { "name": "filename", "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fileUrl", - "storageKey": null - }, { "alias": null, "args": null, @@ -244,6 +236,6 @@ return { }; })(); -(node as any).hash = "abae73e44e966689cd837465f93abcb3"; +(node as any).hash = "8e97649ca88af04aa1184e68a126cc36"; export default node; diff --git a/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment_evidence.graphql.ts b/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment_evidence.graphql.ts index b27c63d98..af33fe884 100644 --- a/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment_evidence.graphql.ts +++ b/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabFragment_evidence.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<60897aba5bbafb70288358e56b0a442a>> * @lightSyntaxTransform * @nogrep */ @@ -13,7 +13,6 @@ export type EvidenceType = "FILE" | "LINK"; import { FragmentRefs } from "relay-runtime"; export type MeasureEvidencesTabFragment_evidence$data = { readonly createdAt: any; - readonly fileUrl: string | null | undefined; readonly filename: string; readonly id: string; readonly mimeType: string; @@ -67,13 +66,6 @@ const node: ReaderFragment = { "name": "createdAt", "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fileUrl", - "storageKey": null - }, { "alias": null, "args": null, @@ -86,6 +78,6 @@ const node: ReaderFragment = { "abstractKey": null }; -(node as any).hash = "4fa5951157f130a8ed644151ef5facd6"; +(node as any).hash = "4b3289f3cb9d29cb48f1a370d943cd0e"; export default node; diff --git a/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabQuery.graphql.ts b/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabQuery.graphql.ts index 2348c2904..6c959f6ad 100644 --- a/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabQuery.graphql.ts +++ b/apps/console/src/pages/organizations/measures/tabs/__generated__/MeasureEvidencesTabQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -219,13 +219,6 @@ return { "name": "filename", "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fileUrl", - "storageKey": null - }, { "alias": null, "args": null, @@ -343,16 +336,16 @@ return { ] }, "params": { - "cacheID": "61b9596f07d5d2516f50e2f646eb21a2", + "cacheID": "9899e64b590ff4c76ca994845611d77a", "id": null, "metadata": {}, "name": "MeasureEvidencesTabQuery", "operationKind": "query", - "text": "query MeasureEvidencesTabQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: EvidenceOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MeasureEvidencesTabFragment_16fISc\n id\n }\n}\n\nfragment MeasureEvidencesTabFragment_16fISc on Measure {\n id\n evidences(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n filename\n fileUrl\n mimeType\n ...MeasureEvidencesTabFragment_evidence\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n fileUrl\n mimeType\n}\n" + "text": "query MeasureEvidencesTabQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: EvidenceOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MeasureEvidencesTabFragment_16fISc\n id\n }\n}\n\nfragment MeasureEvidencesTabFragment_16fISc on Measure {\n id\n evidences(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n filename\n mimeType\n ...MeasureEvidencesTabFragment_evidence\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n mimeType\n}\n" } }; })(); -(node as any).hash = "abae73e44e966689cd837465f93abcb3"; +(node as any).hash = "8e97649ca88af04aa1184e68a126cc36"; export default node; diff --git a/packages/helpers/src/dom.ts b/packages/helpers/src/dom.ts index ed5f24e7d..4df087d97 100644 --- a/packages/helpers/src/dom.ts +++ b/packages/helpers/src/dom.ts @@ -5,3 +5,18 @@ export function withViewTransition(fn: () => void) { } document.startViewTransition(fn); } + +export function downloadFile(url: string | undefined | null, filename: string) { + if (!url) { + alert("Cannot download this file, fileUrl is not provided"); + return; + } + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("hidden", "hidden"); + link.setAttribute("download", filename); + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index 921ce3752..58246ae9b 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -6,7 +6,7 @@ export { getRiskLikelihoods, getSeverity, } from "./risk"; -export { withViewTransition } from "./dom"; +export { withViewTransition, downloadFile } from "./dom"; export { times, groupBy, isEmpty } from "./array"; export { randomInt } from "./number"; export { getMeasureStateLabel, measureStates } from "./measure";