Compare commits

..

12 Commits

Author SHA1 Message Date
arussac
f669733c29 Ajout de marges inférieures pour améliorer l'espacement des éléments dans le style du projet.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 3s
2025-04-28 08:14:04 +02:00
arussac
62439c0e90 Ajout de l'ID à l'élément de téléchargement de l'application et affichage du lien au chargement de la page.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 3s
2025-04-27 16:01:56 +02:00
arussac
73b9b199fd Ajout d'un lien pour télécharger l'application et ajout du fichier APK.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 6s
2025-04-27 15:58:02 +02:00
3927e2f857 Ajout de la description du projet dans le carousel avec un style de texte amélioré.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 3s
2025-04-27 15:12:10 +02:00
c9579f6346 Ajout de la gestion des projets actifs pour la caméra : vérification avant de démarrer un nouveau timelapse, réinitialisation lors de l'arrêt de la caméra et validation lors de l'upload manuel.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 2s
2025-04-27 15:07:26 +02:00
c43e8d53f7 Amélioration de la fonction start_timelapse : ajout de plusieurs formats d'ID pour le backend et logs pour le débogage.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 2s
2025-04-27 14:59:24 +02:00
arussac
2dae04098d Ajout d'une marge inférieure pour la section vidéo afin d'améliorer l'espacement visuel.
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 3s
2025-04-27 12:39:19 +02:00
2097bbfaec Merge pull request 'FUSIOONNNNN' (#1) from dev into main
All checks were successful
SSH Frontend Deploy / ssh-deploy (push) Successful in 3s
Reviewed-on: #1
2025-04-27 12:36:19 +02:00
17560f5e66 Merge branch 'main' into dev 2025-04-27 12:36:06 +02:00
091133ca99 Amélioration des messages d'alerte et gestion des erreurs pour la configuration et l'arrêt de la caméra. Ajout de vérifications pour empêcher la suppression de projets en cours de capture. 2025-04-27 12:34:59 +02:00
arussac
178796edb4 Ajout de styles pour le conteneur de boutons et d'un message d'explication dans la page de détails du projet. 2025-04-27 11:49:08 +02:00
81c254289d chore: update code structure for improved readability and maintainability 2025-04-27 11:48:05 +02:00
9 changed files with 293 additions and 41 deletions

BIN
app-release.apk Normal file

Binary file not shown.

View File

@@ -5,7 +5,7 @@ body::before {
left: 0;
width: 100%;
height: 100%;
background-image: url("../image/camera-image");
background-image: url("../image/camera-image.png");
background-position: center;
background-size: cover;
filter: blur(1px) brightness(0.3);
@@ -444,3 +444,29 @@ footer {
color: white;
font-size: 10vh;
}
/* Styles pour les statuts des projets dans le carrousel */
.status-draft {
color: #6B8E23; /* Vert haricot/marron pour les brouillons */
font-weight: bold;
}
.status-capturing {
color: #87CEEB; /* Bleu ciel pour les projets en capture/en cours */
font-weight: bold;
}
.status-idle {
color: #32CD32; /* Vert vif pour les projets terminés */
font-weight: bold;
}
.status-stopping {
color: #FF4500; /* Orange rougeâtre pour les projets arrêtés */
font-weight: bold;
}
.status-unknown {
color: #cccccc; /* Gris pour les statuts inconnus */
font-style: italic;
}

View File

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 285 KiB

View File

@@ -2,6 +2,7 @@
float: left;
display: inline;
width: 49%;
margin-bottom: 5vh;
}
.picture_placeHolder {
@@ -177,3 +178,11 @@
margin-top: 20px;
margin-bottom: 20px;
}
.video {
margin-bottom: 2vh;
}
#content1 {
margin-bottom: 5vh;
}

View File

@@ -10,7 +10,7 @@
</head>
<body id="body">
<button class="default-button" id="download"><span>Télécharger l'App</span></button>
<a id="app" href="./app-release.apk"><button class="default-button" id="download" ><span>Télécharger l'App</span></button></a>
<button class="default-button" id="show-form-button"><span>Ajouter un projet</span></button>
<div id="customAlert" class="modal">
<div class="modal-content">

View File

@@ -1,6 +1,7 @@
// Global variables
let global_project_list;
let current_project = "";
let active_camera_project = null; // ID du projet actuellement utilisé par la caméra
function formatDate(isoString) {
const date = new Date(isoString);
@@ -184,11 +185,27 @@ async function renderVideo(id){
async function start_timelapse(id,frequency,nbimages){
try {
// Vérifier si un autre projet est déjà actif avec la caméra
if (active_camera_project !== null && active_camera_project !== id) {
const confirmation = confirm(`Un autre projet (ID: ${active_camera_project}) est déjà en cours avec la caméra. Voulez-vous arrêter ce projet et configurer la caméra pour le projet actuel?`);
if (!confirmation) {
alert("Configuration annulée. La caméra reste configurée pour le projet précédent.");
return false;
}
// Si l'utilisateur confirme, on continue et on va redéfinir active_camera_project
}
// Utilisation de plusieurs formats possibles pour garantir que le backend reçoit l'ID
const mydata = JSON.stringify({
projectId: id,
project_id: id, // Format avec underscore (convention standard REST)
projectId: id, // Format camelCase
id: id, // Format simple
interval: frequency,
nb_images: nbimages
});
console.log("Données envoyées à /procedure/start:", mydata); // Log pour débogage
const response = await $.ajax({
url: api_url.concat(`/procedure/start`),
method: "POST",
@@ -197,8 +214,14 @@ async function start_timelapse(id,frequency,nbimages){
data: mydata
});
alert("data retrieval started:", response);
// Définir ce projet comme le projet actif pour la caméra
active_camera_project = id;
alert("Configuration de la caméra réussie");
return response;
} catch (error) {
console.error("Erreur détaillée:", error); // Log plus détaillé de l'erreur
alert("Erreur lors de la configuration de la caméra: " + error);
throw error;
}
}
@@ -216,9 +239,50 @@ async function stopCamera(id){
data: mydata
});
alert("Camera stopped succesfully :", response);
// Réinitialiser le statut du projet actif sur la caméra
active_camera_project = null;
alert("La caméra a été arrêtée avec succès");
return response;
} catch (error) {
alert("Error stopping the camera :", error);
alert("Erreur lors de l'arrêt de la caméra : " + error);
throw error;
}
}
async function manualUpload(imageFile, projectId, timestamp, temperature, humidity) {
try {
// Vérifier que l'upload est fait pour le projet actif de la caméra
if (active_camera_project === null) {
alert("Aucun projet n'est actif avec la caméra. Veuillez configurer un projet avant d'effectuer un upload manuel.");
return false;
}
if (projectId !== active_camera_project) {
alert(`L'upload manuel n'est possible que pour le projet actif (ID: ${active_camera_project}). Vous essayez d'uploader pour le projet ${projectId}.`);
return false;
}
// Create FormData to send the file and metadata
const formData = new FormData();
formData.append('image', imageFile);
formData.append('projectId', projectId);
formData.append('timestamp', timestamp);
formData.append('temperature', temperature);
formData.append('humidity', humidity);
const response = await $.ajax({
url: api_url.concat("/camera/upload"),
method: "POST",
data: formData,
processData: false, // Prevents jQuery from converting FormData to string
contentType: false, // Lets browser set the content type with boundary
});
alert("Image téléchargée avec succès");
return response;
} catch (error) {
alert("Erreur lors du téléchargement de l'image : " + error);
throw error;
}
}

View File

@@ -9,18 +9,57 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
function formatDateToFrench(dateString) {
const options = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
const date = new Date(dateString);
return date.toLocaleDateString('fr-FR', options);
}
function formatStatusWithColor(status) {
let statusText = "";
let colorClass = "";
switch (status) {
case 0:
statusText = "Brouillon";
colorClass = "status-draft";
break;
case 1:
statusText = "En capture";
colorClass = "status-capturing";
break;
case 2:
statusText = "Terminé";
colorClass = "status-idle";
break;
case 3:
statusText = "En cours d\'arrêt";
colorClass = "status-stopping";
break; // Ajout du break manquant ici
default:
statusText = "Inconnu";
colorClass = "status-unknown";
}
return `<span class="${colorClass}">${statusText}</span>`;
}
function formatStatus(status) {
switch (status) {
case 0:
return "brouillon";
return "Brouillon"; // Projet à peine créé, vide sans config
case 1:
return "en cours";
return "En capture"; // Projet en cours de capture
case 2:
return "terminé";
return "En pause"; // Projet en pause ou inactif
case 3:
return "En cours de génération";
return "En cours d\'arrêt"; // Projet en cours d'arrêt
default:
return "inconnu";
return "Inconnu";
}
}
@@ -91,10 +130,11 @@ function setupCarousel(global_project_list) {
projectDiv.classList.add(letter[index]);
projectDiv.innerHTML = `
<h2>${project.name}</h2>
<p>${formatDate(project.start_date)}</p>
<p>Status : ${formatStatus(parseInt(project.status))}</p>
<p class="project-description" style="font-size: 0.8em; font-style: italic; margin-top: -5px; margin-bottom: 10px;">${project.description || 'Pas de description'}</p>
<p><strong>Date de début :</strong> ${formatDateToFrench(project.start_date)}</p>
<p><strong>Statut :</strong> ${formatStatusWithColor(parseInt(project.status))}</p>
<div class="project_buttons">
<button class="default-access-button">détails de ${project.name}</button>
<button class="default-access-button">Voir les détails</button>
<button class="default-delete-button">Supprimer</button>
</div>
`;
@@ -111,6 +151,14 @@ function setupCarousel(global_project_list) {
deleteButton.addEventListener('click', (event) => {
event.stopPropagation();
const projectName = project.name;
const projectStatus = parseInt(project.status);
// Vérifier si le projet est en cours de capture (statut 1) ou en cours d'arrêt (statut 3)
if (projectStatus === 1 || projectStatus === 3) {
alert(`Impossible de supprimer "${projectName}" car sa capture est ${projectStatus === 1 ? 'en cours' : 'en cours d\'arrêt'}.\nVeuillez d'abord arrêter la procédure de capture.`);
return;
}
document.getElementById('alertMessage').textContent = `Veux-tu vraiment supprimer le projet : ${projectName} ?`;
document.getElementById('customAlert').style.display = 'block';
@@ -176,6 +224,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
child.style.display = 'none';
}
});
document.getElementById('app').style.display = 'block';
const newH1 = document.createElement('h1');
newH1.textContent = "Caméra Timelapse sur mobile, utilisez l'app !!!";
newH1.classList.add('global_title_h1');

View File

@@ -30,6 +30,40 @@ document.addEventListener("DOMContentLoaded", async () => {
const firstInput = document.getElementById("first");
const lastInput = document.getElementById("last");
const formContainerUpload = document.getElementById("form-container-upload");
const showFormButtonUpload = document.getElementById("show-form-button-upload");
const closeFormButtonUpload = document.getElementById("close-form-button-upload");
const manualUploadForm = document.getElementById("manual-upload-form");
if (formContainerUpload && showFormButtonUpload && closeFormButtonUpload && manualUploadForm) {
showFormButtonUpload.addEventListener("click", () => {
formContainerUpload.style.display = "flex";
});
closeFormButtonUpload.addEventListener("click", () => {
formContainerUpload.style.display = "none";
});
manualUploadForm.addEventListener("submit", async (event) => {
event.preventDefault();
const imageFile = document.getElementById("imageFile").files[0];
const temperature = document.getElementById("temperature").value;
const humidity = document.getElementById("humidity").value;
const timestamp = new Date().toISOString();
try {
await manualUpload(imageFile, projectId, timestamp, temperature, humidity);
alert("Image uploadée avec succès !");
formContainerUpload.style.display = "none";
} catch (error) {
alert("Erreur lors de l'upload de l'image : " + error);
}
});
} else {
console.error("Un ou plusieurs éléments du formulaire d'upload manuel sont introuvables.");
}
let selectedNumbers = [];
populateTimelapseLogic(start_timelapse_button, projectId).then(() => {
@@ -52,9 +86,13 @@ document.addEventListener("DOMContentLoaded", async () => {
const frequency = days * 1440 + hours * 60 + minutes;
if(frequency >= 3) {
const nbrimages = document.getElementById("totalImages").value;
start_timelapse(projectId, frequency, nbrimages).then(() => {
try {
// Attendre que la requête API soit terminée avant de recharger la page
await start_timelapse(projectId, frequency, nbrimages);
location.reload();
});
} catch (error) {
console.error("Erreur lors du démarrage du timelapse:", error);
}
} else {
alert("La fréquence doit être supérieure à 3 minutes !");
}
@@ -64,8 +102,13 @@ document.addEventListener("DOMContentLoaded", async () => {
document
.getElementById("stop-camera")
.addEventListener("click", async () => {
stopCamera(projectId);
try {
// Attendre que la requête API soit terminée avant de recharger la page
await stopCamera(projectId);
location.reload();
} catch (error) {
console.error("Erreur lors de l'arrêt de la caméra:", error);
}
});
}
});
@@ -345,9 +388,14 @@ async function generateViewMetric(projectId) {
if (measurements != 404) {
let samples;
if (videoId != -1) {
try {
console.log("videoId", videoId);
currentVideoDatas = await getDataVideoFromApi(videoId);
samples = JSON.parse(currentVideoDatas[0]["measurement_ids"]);
if (currentVideoDatas[0].status != 0) {
console.log("currentVideoDatas", currentVideoDatas);
// Vérifier que currentVideoDatas existe et contient au moins un élément
if (currentVideoDatas) {
samples = JSON.parse(currentVideoDatas["measurement_ids"]);
if (currentVideoDatas.status != 0) {
videoPlaceHolder.innerHTML = `
<video class="video" controls>
<source src="${api_url}/videos/file/${videoId}" type="video/mp4">
@@ -363,6 +411,21 @@ async function generateViewMetric(projectId) {
showConfirmationAlert(videoId);
});
tempoMeasure = filterAndSortMeasurementsByNumber(measurements, samples);
} else {
// Si aucune donnée n'est disponible pour cette vidéo
console.warn("Aucune donnée disponible pour la vidéo sélectionnée");
deletePlaceHolder.innerHTML = "";
videoPlaceHolder.innerHTML = `<h2>Données de vidéo non disponibles</h2>`;
samples = measurements.map((measurement) => measurement.id);
tempoMeasure = filterAndSortMeasurementsByIds(measurements, samples);
}
} catch (error) {
console.error("Erreur lors de la récupération des données vidéo:", error);
deletePlaceHolder.innerHTML = "";
videoPlaceHolder.innerHTML = `<h2>Erreur lors de la récupération des données vidéo</h2>`;
samples = measurements.map((measurement) => measurement.id);
tempoMeasure = filterAndSortMeasurementsByIds(measurements, samples);
}
} else {
deletePlaceHolder.innerHTML = "";
videoPlaceHolder.innerHTML = "";
@@ -496,10 +559,19 @@ function filterAndSortMeasurementsByNumber(measurements, Numbers) {
function checkVideoPath(videos, name) {
let res = true;
// Vérifier si videos est un tableau et s'il a une méthode forEach
if (videos && Array.isArray(videos) && videos.length > 0) {
videos.forEach((video) => {
const videoName = video.name;
if (videoName == name) res = false;
});
} else {
// Si videos n'est pas un tableau valide, on considère qu'aucune vidéo n'existe
// et donc n'importe quel nom est valide
console.log("Aucune vidéo existante détectée");
}
return res;
}
@@ -540,6 +612,16 @@ async function populateTimelapseLogic(placeholder, id) {
placeholder.innerHTML = `<button class="default-delete-button" id="stop-camera">
<span> Stopper la prise d'images </span>
</button>`;
} else if (data.status == 2) {
// Ajout de la possibilité de configurer la caméra pour un projet avec statut "terminé"
placeholder.innerHTML = `<button class="default-button" id="show-form-button-project">
<span> Reconfigurer la caméra </span>
</button>`;
} else if (data.status == 3) {
// Affichage d'un message pour indiquer que l'arrêt est en cours
placeholder.innerHTML = `<button class="default-button" disabled>
<span> Arrêt en cours... </span>
</button>`;
} else {
placeholder.innerHTML = ``;
}

View File

@@ -108,6 +108,27 @@
<button id="commencer" class="default-access-button">Lancer</button>
</div>
</div>
<!-- Formulaire pour l'upload manuel d'une image -->
<div id="form-container-upload" class="form-container" style="display: none">
<div class="form-content">
<div class="form-header">
<button id="close-form-button-upload" class="default-delete-button">&#x2715;</button>
</div>
<h1>Upload Manuel</h1>
<form id="manual-upload-form">
<label for="imageFile">Fichier image :</label>
<input type="file" id="imageFile" name="imageFile" accept="image/*" required />
<br /><br />
<label for="temperature">Température :</label>
<input type="number" id="temperature" name="temperature" step="0.1" required />
<br /><br />
<label for="humidity">Humidité :</label>
<input type="number" id="humidity" name="humidity" step="0.1" required />
<br /><br />
<button type="submit" class="default-access-button">Uploader</button>
</form>
</div>
</div>
<!-- page classique -->
<div>
<div>
@@ -118,6 +139,7 @@
</div>
<div id="start-timelapse"></div>
<button class="default-access-button" id="show-form-button-camera">+</button>
<button class="default-access-button" id="show-form-button-upload">Upload Manuel</button>
<div id="delete-placeholder"></div>
</div>
</div>