ldc-com-app-bo/resources/ts/bo/components/CrudDocumentInputComponent.vue
2025-06-17 12:29:15 +02:00

410 lines
11 KiB
Vue

<template>
<div :class="`crud-document-input-${intId} row`">
<!-- FILE LANGUAGE -->
<div class="col-12 col-md-6 mb-3">
<label
for="selectLanguageDocument"
class="col-form-label"
>
<b>{{ trans.methods.__("Langue du document") }} *</b>
</label>
<select
id="selectLanguageDocument"
name="lang"
class="form-select"
aria-label="Select language for the document"
@click="languageDocument($event)"
role="button"
>
<option
v-for="(language, languageIndex) in intLanguages"
:key="languageIndex"
:value="language.value"
:selected="Number(intModel?.lang) === language.value ? true : false"
>
{{ language.label }}
</option>
</select>
<small class="form-text text-muted">
{{ trans.methods.__("Choisissez une langue parmis la liste.") }}
</small>
</div>
<!-- INPUT NAME -->
<div class="col-12 col-md-6 mb-3">
<label
for="documentTitle"
class="col-form-label"
>
<b>{{ trans.methods.__("Titre du document") }} *</b>
</label>
<WordCounter
id="documentTitle"
class="ck-word-count"
/>
<input
id="documentTitle"
type="text"
name="title"
class="form-control need-word-counter"
:placeholder="trans.methods.__('Un beau titre de document')"
v-model="documentTitle"
>
<small class="form-text text-muted">
{{ trans.methods.__("Doit posséder 3 caractères minimum, 255 maximum.") }}
</small>
</div>
<!-- SELECT DIRECTORY -->
<div
v-if="intIsDocFR"
class="col-12 col-md-6 mb-3"
>
<label
for="directoryDirectoryId"
class="col-form-label"
>
<b>{{ trans.methods.__("Dossier à associer") }} *</b>
</label>
<select
id="directoryDirectoryId"
class="form-select"
name="directory_id"
aria-describedby="directoryDirectoryIdHelp"
role="button"
>
<option
v-for="(directory, directoryIndex) in intDirectories"
:key="directoryIndex"
:value="directory.id"
:selected="intModel?.directory_id === directory.id ? true : false"
>
{{ directory.name }}
</option>
</select>
<small class="form-text text-muted">
{{ trans.methods.__("Choisissez un dossier à associer parmis la liste.") }}
</small>
</div>
<!-- SELECT FRENCH DOCUMENT -->
<div
v-else
class="col-12 col-md-6 mb-3"
>
<label
for="documentParentId"
class="col-form-label"
>
<b>{{ trans.methods.__("Document français à associer") }} *</b>
</label>
<select
v-if="intDocuments.length > 0"
id="documentParentId"
class="form-select"
name="document_id"
aria-describedby="documentParentIdHelp"
role="button"
>
<option
v-for="(document, documentIndex) in intDocuments"
:key="documentIndex"
:value="document.id"
:selected="intModel?.document_id === document.id ? true : false"
>
{{ document.title }}
</option>
</select>
<p
v-else
class="text-muted"
>
{{ trans.methods.__("Aucun documents français n'est disponible") }}
</p>
<small class="form-text text-muted">
{{ trans.methods.__("Choisissez un document français à associer parmis la liste.") }}
</small>
</div>
<!-- CHOOSE FILE -->
<div class="col-12 col-md-6 mb-3">
<label
for="documentInputFile"
class="col-form-label"
>
<b>{{ trans.methods.__("Document à ajouter") }} *</b>
</label>
<div :class="intIsUploading ? 'd-none' : ''">
<div class="input-group">
<button
class="btn btn-outline-secondary"
type="button"
data-bs="tooltip"
:title="trans.methods.__('Modifier le document source')"
>
<FontAwesomeIcon icon="fa-solid fa-folder-open" />
</button>
<input
type="text"
class="form-control"
role="button"
ref="inputFile"
:value="
intDocumentDatas.path
? intDocumentDatas.path
: 'Sélectionnez un fichier'
"
data-bs="tooltip"
:title="trans.methods.__('Modifier le document source')"
readonly
>
</div>
<div v-if="intMessage">
<p
class="m-0"
:class="intSuccess ? 'text-success' : 'text-danger'"
>
{{ intMessage }}
</p>
</div>
</div>
<div
class="progress"
:class="!intIsUploading ? 'd-none' : ''"
>
<div
class="progress-bar progress-bar-striped progress-bar-animated"
ref="progressBar"
role="progressbar"
aria-label="Progress bar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
>
{{ intFilePercent }}%
</div>
</div>
<small class="form-text text-muted">
{{ trans.methods.__("Doit être au format .pdf ou .mp4.") }}
</small>
</div>
<input
class="d-none"
name="path"
:value="intDocumentDatas.path"
>
<input
class="d-none"
name="uuid"
:value="intDocumentDatas.uuid"
>
<input
class="d-none"
name="type"
:value="intDocumentDatas.type"
>
</div>
</template>
<script lang="ts" setup>
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { Tooltip } from "bootstrap";
import Resumable from "resumablejs";
import { nextTick, onMounted, ref, useAttrs } from "vue";
import route from "./../../modules/route";
import trans from "./../../modules/trans";
import WordCounter from "./WordCounterComponent.vue";
defineOptions({
name: "AddDocumentComponent"
});
// * ATTRIBUTES
const attrs = useAttrs();
// * REFS
const inputFile = ref<HTMLInputElement|null>(null);
const progressBar = ref<HTMLDivElement|null>(null);
// * DATA
const intId = ref<string>("");
const intMessage = ref<string>("");
const intSuccess = ref<boolean>(false);
const intIsUploading = ref<boolean>(false);
const intFilePercent = ref<number|null>(null);
const intProgressbar = ref<HTMLDivElement|null>(null);
const intLanguages = ref<Array<{
value: number;
label: string;
}>>([]);
const intDocuments = ref<Array<{
id: number;
title: string;
}>>([]);
const intDirectories = ref<LaravelModel[]>([]);
const intIsDocFR = ref<boolean>(false);
const intModel = ref<LaravelModel|null>(null);
const intR = ref<Resumable|null>(null);
const intDocumentDatas = ref<{
directory_id: number;
document_id: number | null;
uuid: string;
title: string;
type: string;
path: string;
lang: string;
published: boolean;
}>({
directory_id: 0,
document_id: null,
uuid: "",
title: "",
type: "",
path: "",
lang: "",
published: false,
});
const intCsrf = ref<string>("");
const intTooltipList = ref<HTMLButtonElement[]>([]);
let documentTitle = ref<string>("");
onMounted(() => {
const json = String(attrs.json ?? "{}"),
data = JSON.parse(json);
intId.value = data.id;
intLanguages.value = Object.values(data.languages);
intDocuments.value = data.documents;
intDirectories.value = data.directories;
intModel.value = data.model;
if (intModel.value) {
documentTitle.value = String(intModel.value.title ?? "");
}
if (intModel.value != null) {
if (intModel.value.lang === undefined) {
intIsDocFR.value = true;
} else {
intDocumentDatas.value.path = String(intModel.value.path);
intDocumentDatas.value.uuid = String(intModel.value.uuid);
intDocumentDatas.value.type = String(intModel.value.type);
intIsDocFR.value = intModel.value.lang === 1 ? true : false;
}
}
intCsrf.value = data.csrf;
nextTick(() => {
initResumable();
updateBootstrapTooltip();
});
});
function languageDocument(e: Event) {
const target = e.target as HTMLSelectElement;
intIsDocFR.value = Number(target.value) === intLanguages.value[0].value ? true : false;
}
function initResumable() {
// Route.
const uploadFileRoute = route.methods.route("bo.documents.upload");
if (!uploadFileRoute) {
throw new Error("Undefined route bo.documents.upload");
}
intR.value = new Resumable({
chunkSize: 1024 * 1024, // 1MB
simultaneousUploads: 1,
maxFiles: 1,
testChunks: false,
target: uploadFileRoute,
query: {
_token: intCsrf.value,
},
});
intR.value.on("fileAdded", fileAdded);
intR.value.on("fileSuccess", fileSuccess);
intR.value.on("fileError", fileError);
intR.value.on("fileProgress", () => {
if (intR.value !== null) {
intFilePercent.value = Math.floor(intR.value.progress() * 100);
if (intProgressbar.value !== null)
intProgressbar.value.style.width = intFilePercent.value + "%";
}
});
intR.value.assignBrowse(inputFile.value as Element, false);
}
/**
*
* @param file The file would like to store.
*/
function fileAdded(file: any) {
if (
file.file.type !== "application/pdf" &&
file.file.type !== "video/mp4"
) {
return !(intMessage.value = "Extension du fichier invalide.");
}
intIsUploading.value = true;
setTimeout(() => {
intProgressbar.value = progressBar.value as HTMLDivElement;
intProgressbar.value.style.width = intFilePercent.value + "%";
}, 10);
closeBootstrapTooltip();
if (intR.value !== null) intR.value.upload();
}
/**
* Run method if the file is stored.
* @param file The file stored.
* @param message Set a new successful message.
*/
function fileSuccess(file: any, message: string) {
let result = JSON.parse(message);
intDocumentDatas.value.path = file.fileName;
intDocumentDatas.value.uuid = result.uid;
if (file.file.type === "application/pdf") {
intDocumentDatas.value.type = "pdf";
} else {
intDocumentDatas.value.type = "mp4";
}
intSuccess.value = true;
intMessage.value = "Fichier chargé.";
const intInputFileResult = progressBar.value as HTMLInputElement;
intInputFileResult.value =
intDocumentDatas.value.uuid + intDocumentDatas.value.type;
setTimeout(() => {
intIsUploading.value = false;
nextTick(() => {
updateBootstrapTooltip();
});
}, 800);
}
/**
* Set a new error message.
*/
function fileError() {
intMessage.value = "Une erreur est survenue, veuillez recharger la page.";
}
/**
* Update Bootstrap tooltips.
*/
function updateBootstrapTooltip() {
let newTooltipList = [].slice.call(
document.querySelectorAll(
".crud-document-input-" + intId.value + " [data-bs=\"tooltip\"]"
)
) as HTMLButtonElement[];
let tmp = newTooltipList.filter((x) => !intTooltipList.value.includes(x));
tmp.map((tooltip) => {
return new Tooltip(tooltip);
});
intTooltipList.value = newTooltipList;
closeBootstrapTooltip();
}
/**
* Close all Bootstrap tooltips.
*/
function closeBootstrapTooltip() {
intTooltipList.value.forEach((tooltip) => {
tooltip.blur();
Tooltip.getInstance(tooltip)?.hide();
});
}
</script>