ldc-startcoq/resources/ts/components/MultipleDocumentsInputComponent.vue
2023-09-11 16:55:46 +02:00

362 lines
12 KiB
Vue

<template>
<div
:class="`container multiple-documents-input multiple-documents-input-${intId} p-0`"
>
<!-- Main buttons -->
<div class="row">
<div class="col-12 d-flex form-group">
<div class="input-group">
<button
@click.prevent="($refs.actualDocuments as HTMLInputElement).click()"
class="btn btn-secondary"
type="button"
:title="__('Modifier les documents sources')"
data-bs-toggle="tooltip"
:disabled="intValues.length >= intItemLimit[1]"
>
<i class="fa-solid fa-folder-open" />
</button>
<input
:id="intId"
ref="actualDocuments"
@change="loadDocuments([].slice.call(($refs.actualDocuments as HTMLInputElement).files))"
type="file"
class="d-none"
name="multipleInputDocuments"
:accept="intAccepts"
:aria-describedby="`Help${intId}`"
multiple
>
<input
@click.prevent="($refs.actualDocuments as HTMLInputElement).click()"
type="text"
:value="
'Actuellement ' +
intValues.length +
'/' +
intItemLimit[1] +
' documents sont présents'
"
class="form-control right-aligned"
:title="__('Modifier les documents sources')"
data-bs-toggle="tooltip"
:aria-describedby="`Help${intId}`"
:disabled="intValues.length >= intItemLimit[1]"
readonly
>
<button
v-if="intValues.length > intItemLimit[0]"
@click.prevent="removeFiles"
class="btn btn-danger"
type="button"
:title="__('Supprimer tous les documents')"
data-bs-toggle="tooltip"
:disabled="intValues.length <= 0"
>
<i class="fas fa-eraser" />
</button>
</div>
</div>
</div>
<!-- End main buttons -->
<!-- Documents list -->
<div
v-for="(value, valueIndex) of intValues"
:key="valueIndex"
class="row w-100 mx-auto my-2"
>
<div class="col-12 form-group text-center px-1">
<div
class="d-flex justify-content-between align-items-center pb-1"
:class="valueIndex != 1 ? 'border-top' : ''"
>
<label
v-if="value"
class="col-form-label d-flex"
>
<span
class="text-start text-ellipsis"
v-if="intOriginalFileName[valueIndex]"
>
{{ intOriginalFileName[valueIndex].split('.').pop() }}
&nbsp;
</span>
<span class="text-start text-ellipsis">
{{ intOriginalFileName[valueIndex] }}
</span>
</label>
<small
v-if="value"
:id="`PreviewHelp${intId}`"
class="form-text text-muted d-block m-0"
>
<span v-if="intOriginalFile[valueIndex]">
{{ humanFileSize(intOriginalFile[valueIndex].size) }}.
</span>
</small>
<div class="input-group justify-content-center mt-1 w-auto">
<button
@click.prevent="viewDocumentSource(valueIndex)"
class="btn btn-warning"
type="button"
:title="__('Afficher le document')"
data-bs-toggle="tooltip"
>
<i class="fa-solid fa-eye" />
</button>
<input
:id="intId"
ref="actualDocument"
@change="changedDocument(($refs.actualDocument as HTMLInputElement[])[valueIndex], valueIndex)"
type="file"
class="d-none"
:name="intName.replace('[]', '') + `[${valueIndex}]`"
:accept="intAccepts"
:aria-describedby="`Help${intId}`"
:disabled="intPreviousFiles.has(intOriginalFileName[valueIndex])"
>
<input
@change="changedDocument(($refs.actualDocument as HTMLInputElement[])[valueIndex], valueIndex)"
type="text"
class="d-none"
:name="intName.replace('[]', '') + `[${valueIndex}]`"
:aria-describedby="`Help${intId}`"
:disabled="!intPreviousFiles.has(intOriginalFileName[valueIndex])"
:value="intValues[valueIndex]"
>
<button
v-if="intValues.length > intItemLimit[0]"
@click.prevent="removeFile(valueIndex)"
class="btn btn-danger"
type="button"
:title="__('Supprimer le document')"
data-bs-toggle="tooltip"
>
<i class="fas fa-trash" />
</button>
</div>
</div>
<p class="text-danger m-0">
{{
parseValidationInput(
`${intName.replace(/[\[\]']+/g, "")}.${n - 1}`,
intAllErrors
)
}}
</p>
</div>
</div>
<!-- End documents list -->
<small
:id="`Help${intId}`"
class="form-text text-muted"
>
{{ intHelper }}
</small>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import bsTooltips from "./../modules/bsTooltips";
import error from "./../modules/parseLaravelErrorMessages";
import trans from "./../modules/trans";
const UNITS = [
"byte",
"kilobyte",
"megabyte",
"gigabyte",
"terabyte",
"petabyte",
];
const BYTES_PER_KB = 1024;
export default defineComponent({
name: "MultipleDocumentsInputComponent",
mixins: [error, trans, bsTooltips],
components: {
},
data(): {
/** The file ext accepted */
intAccepts: string;
/** The file ext accepted */
intId: string;
intName: string;
intPreviousFiles: Set<string>;
intOriginalFile: File[];
intOriginalFileName: string[];
intValues: string[];
intHelper: string;
intTooltipList: HTMLButtonElement[];
intViewerLoadDocument: boolean;
intItemLimit: number[];
intAllErrors: Record<string, string[]>;
} {
return {
intAccepts: ".xls,.csv,.xlsx,.png,.jpg,.jpeg,.gif,.doc,.docx,.ppt,.pdf,.txt",
intId: "",
intName: "",
intPreviousFiles: new Set<string>(),
intOriginalFile: [],
intOriginalFileName: [],
intValues: [],
intHelper: "",
intTooltipList: [],
intViewerLoadDocument: false,
intItemLimit: [0, 0],
intAllErrors: {},
};
},
mounted() {
const json = String(this.$attrs.json ?? "");
const data = JSON.parse(json);
this.intId = data.id;
this.intName = data.name;
this.intValues = data.value;
this.intHelper = data.helper ?? "";
this.intItemLimit = data.limit;
this.intAllErrors = data.errors ?? {};
this.$nextTick(() => {
this.initComponent();
});
this.$nextTick(() => {
this.initTooltips();
});
},
computed: {
documentInputs(): HTMLInputElement[] {
return this.$refs.actualDocument as HTMLInputElement[];
}
},
methods: {
/** Initialize the component with previously documents registered. */
initComponent() {
if (this.intValues.length > 0) {
this.intValues.forEach((element, index) => {
element.charAt(0) != "/" ? "/".concat(element) : "";
this.intValues.splice(index, 1, element.replace(/^\/|^/, "/"));
});
this.initDocumentsSaved();
}
},
/** Assign previously documents registered to respective input. */
initDocumentsSaved() {
this.intValues.forEach((element, index) => {
const documentName = element.substring(element.lastIndexOf("/") + 1);
this.intPreviousFiles.add(documentName);
this.createFile(
element,
`${documentName}`
).then((file: File) => {
const container = new DataTransfer();
container.items.add(file);
this.documentInputs[index].files = container.files;
this.intOriginalFile[index] = container.files[0];
this.intOriginalFileName[index] = container.files[0].name;
});
});
},
/** Create a file from a path. */
async createFile(path: string, name: string): Promise<File> {
const response = await fetch(
window.location.protocol + "//" + window.location.host + "/" + path
);
const data = await response.blob();
const type = await response.headers.get("content-type") ?? "application/pdf";
const metadata = {
type: type,
};
return new File([data], name, metadata);
},
/** Remove one specific document. */
removeFile(n: number) {
if(this.intPreviousFiles.has(this.intOriginalFileName[n])) {
this.intPreviousFiles.delete(this.intOriginalFileName[n]);
}
this.intValues.splice(n, 1);
this.intOriginalFile.splice(n, 1);
this.intOriginalFileName.splice(n, 1);
this.documentInputs.forEach((element, index) => {
if (this.intOriginalFile[index] != undefined) {
const dt = new DataTransfer();
dt.items.add(this.intOriginalFile[index]);
element.files = dt.files;
}
});
this.$nextTick(() => this.initTooltips());
},
/** Remove all documents. */
removeFiles() {
this.intValues.length = this.intItemLimit[0];
this.intOriginalFile.length = this.intItemLimit[0];
this.intOriginalFileName.length = this.intItemLimit[0];
this.intAllErrors = {};
this.$nextTick(() => this.initTooltips());
},
/** Change one specific document. */
changedDocument(el: HTMLInputElement, n: number) {
const reader = new FileReader();
if (!el.files) {
throw new Error("No files");
}
reader.addEventListener(
"load",
() => {
// On convertit le document en une chaîne de caractères base64.
this.intValues[n] = String(reader.result);
},
false
);
if (el.files[0]) {
this.intOriginalFile[n] = el.files[0];
this.intOriginalFileName[n] = el.files[0].name;
reader.readAsDataURL(el.files[0]);
}
},
/** Update inputs file. */
loadDocuments(files: Array<File>) {
if (files.length > 0) {
if (this.intValues.length + files.length <= this.intItemLimit[1]) {
const actualFile = files.shift() as File;
const reader = new FileReader();
reader.readAsDataURL(actualFile);
reader.onload = () => {
// On convertit l'document en une chaîne de caractères base64.
this.intValues.push(String(reader.result));
this.intOriginalFileName[this.intValues.length - 1] = "";
};
reader.onloadend = () => {
const dt = new DataTransfer();
dt.items.add(actualFile);
this.documentInputs[this.documentInputs.length - 1].files = dt.files;
this.intOriginalFile[this.documentInputs.length - 1] = actualFile;
this.intOriginalFileName[this.documentInputs.length - 1] = actualFile.name;
this.loadDocuments(files);
this.initTooltips();
};
}
}
},
/** Update document source in the viewer. */
viewDocumentSource(n: number) {
window.open(URL.createObjectURL(this.intOriginalFile[n]));
},
/** Get size of a file. */
humanFileSize(sizeBytes: number | bigint): string {
let size = Math.abs(Number(sizeBytes));
let u = 0;
while (size >= BYTES_PER_KB && u < UNITS.length - 1) {
size /= BYTES_PER_KB;
++u;
}
return new Intl.NumberFormat([], {
style: "unit",
unit: UNITS[u],
unitDisplay: "short",
maximumFractionDigits: 1,
}).format(size);
},
},
});
</script>