Add direct download link feature with new button implementation

Introduce a new feature that allows for direct download links by integrating TempUrlGeneratorInterface. Added new DOWNLOAD_LINK_ONLY group and corresponding logic to generate download links in StoredObjectNormalizer. Implement a new Twig filter and Vue component for rendering the download button. Updated tests to cover the new functionality.
This commit is contained in:
2024-10-08 15:15:15 +02:00
parent 5c0f3cb317
commit 118ae291e2
10 changed files with 162 additions and 13 deletions

View File

@@ -0,0 +1,27 @@
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
import {createApp} from "vue";
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
import {StoredObject, StoredObjectStatusChange} from "../../types";
import {defineComponent} from "vue";
import DownloadButton from "../../vuejs/StoredObjectButton/DownloadButton.vue";
import ToastPlugin from "vue-toast-notification";
const i18n = _createI18n({});
window.addEventListener('DOMContentLoaded', function (e) {
document.querySelectorAll<HTMLDivElement>('div[data-download-button-single]').forEach((el) => {
const storedObject = JSON.parse(el.dataset.storedObject as string) as StoredObject;
const title = el.dataset.title as string;
const app = createApp({
components: {DownloadButton},
data() {
return {storedObject, title, classes: {btn: true, "btn-outline-primary": true}};
},
template: '<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
});
app.use(i18n).use(ToastPlugin).mount(el);
});
});

View File

@@ -2,6 +2,7 @@ import {
DateTime,
User,
} from "../../../ChillMainBundle/Resources/public/types";
import {SignedUrlGet} from "./vuejs/StoredObjectButton/helpers";
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
@@ -30,6 +31,7 @@ export interface StoredObject {
href: string;
expiration: number;
};
downloadLink?: SignedUrlGet;
};
}

View File

@@ -1,5 +1,5 @@
<template>
<a v-if="!state.is_ready" :class="props.classes" @click="download_and_open($event)" title="T&#233;l&#233;charger">
<a v-if="!state.is_ready" :class="props.classes" @click="download_and_open()" title="T&#233;l&#233;charger">
<i class="fa fa-download"></i>
<template v-if="displayActionStringInButton">Télécharger</template>
</a>
@@ -20,7 +20,15 @@ interface DownloadButtonConfig {
atVersion: StoredObjectVersion,
classes: { [k: string]: boolean },
filename?: string,
/**
* if true, display the action string into the button. If false, displays only
* the icon
*/
displayActionStringInButton: boolean,
/**
* if true, will download directly the file on load
*/
directDownload: boolean,
}
interface DownloadButtonState {
@@ -29,13 +37,17 @@ interface DownloadButtonState {
href_url: string,
}
const props = withDefaults(defineProps<DownloadButtonConfig>(), {displayActionStringInButton: true});
const props = withDefaults(defineProps<DownloadButtonConfig>(), {displayActionStringInButton: true, directDownload: false});
const state: DownloadButtonState = reactive({is_ready: false, is_running: false, href_url: "#"});
const open_button = ref<HTMLAnchorElement | null>(null);
function buildDocumentName(): string {
const document_name = props.filename ?? props.storedObject.title ?? 'document';
let document_name = props.filename ?? props.storedObject.title;
if ('' === document_name) {
document_name = 'document';
}
const ext = mime.getExtension(props.atVersion.type);
@@ -46,9 +58,7 @@ function buildDocumentName(): string {
return document_name;
}
async function download_and_open(event: Event): Promise<void> {
const button = event.target as HTMLAnchorElement;
async function download_and_open(): Promise<void> {
if (state.is_running) {
console.log('state is running, aborting');
return;
@@ -75,11 +85,13 @@ async function download_and_open(event: Event): Promise<void> {
state.is_running = false;
state.is_ready = true;
await nextTick();
open_button.value?.click();
console.log('open button should have been clicked');
if (!props.directDownload) {
await nextTick();
open_button.value?.click();
const timer = setTimeout(reset_state, 45000);
console.log('open button should have been clicked');
setTimeout(reset_state, 45000);
}
}
function reset_state(): void {
@@ -87,10 +99,19 @@ function reset_state(): void {
state.is_ready = false;
state.is_running = false;
}
onMounted(() => {
if (props.directDownload) {
download_and_open();
}
});
</script>
<style scoped lang="scss">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
i.fa {
margin-right: 0.5rem;
}
</style>

View File

@@ -161,7 +161,14 @@ async function download_and_decrypt_doc(storedObject: StoredObject, atVersion: n
throw new Error("no version associated to stored object");
}
const downloadInfo= await download_info_link(storedObject, atVersionToDownload);
// sometimes, the downloadInfo may be embedded into the storedObject
console.log('storedObject', storedObject);
let downloadInfo;
if (typeof storedObject._links !== 'undefined' && typeof storedObject._links.downloadLink !== 'undefined') {
downloadInfo = storedObject._links.downloadLink;
} else {
downloadInfo = await download_info_link(storedObject, atVersionToDownload);
}
const rawResponse = await window.fetch(downloadInfo.url);

View File

@@ -0,0 +1 @@
<div data-download-button-single="data-download-button-single" data-stored-object="{{ document_json|json_encode|escape('html_attr') }}" data-title="{{ title|escape('html_attr') }}"></div>