Spaces:
Running
Running
update home view
Browse files- src/views/HomeView.vue +258 -58
src/views/HomeView.vue
CHANGED
@@ -7,6 +7,7 @@ import api from '../services/api'
|
|
7 |
import CalibrationArea from '@/components/CalibrationArea.vue'
|
8 |
import FootballField from '@/components/FootballField.vue'
|
9 |
import PlotlyChart from '@/components/PlotlyChart.vue'
|
|
|
10 |
|
11 |
const router = useRouter()
|
12 |
const calibrationStore = useCalibrationStore()
|
@@ -24,6 +25,16 @@ const manualSection = ref(null)
|
|
24 |
|
25 |
// États locaux
|
26 |
const dragActive = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
// États pour la vue manuelle
|
29 |
const thumbnail = ref(null)
|
@@ -137,6 +148,35 @@ const selectMode = (mode) => {
|
|
137 |
calibrationStore.setMode(mode)
|
138 |
}
|
139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
const resetToStart = () => {
|
141 |
// Réinitialiser tous les stores
|
142 |
calibrationStore.reset()
|
@@ -339,8 +379,19 @@ const restart = () => {
|
|
339 |
}
|
340 |
|
341 |
const exportResults = () => {
|
342 |
-
|
343 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
344 |
|
345 |
const content = JSON.stringify(data, null, 2)
|
346 |
|
@@ -603,65 +654,86 @@ const onChartError = (error) => {
|
|
603 |
</div>
|
604 |
</section>
|
605 |
|
606 |
-
|
607 |
<section v-if="showUpload" ref="uploadSection" class="section upload-section">
|
608 |
-
|
609 |
-
|
610 |
-
class="
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
634 |
</div>
|
635 |
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
<
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
</p>
|
644 |
-
</div>
|
645 |
-
|
646 |
-
<div class="preview" v-if="uploadStore.filePreview">
|
647 |
-
<img
|
648 |
-
v-if="uploadStore.isImage"
|
649 |
-
:src="uploadStore.filePreview"
|
650 |
-
alt="Preview"
|
651 |
-
class="preview-media"
|
652 |
-
>
|
653 |
-
<video
|
654 |
-
v-else
|
655 |
-
:src="uploadStore.filePreview"
|
656 |
-
controls
|
657 |
-
class="preview-media"
|
658 |
>
|
659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
660 |
</div>
|
661 |
-
|
662 |
-
<button @click="uploadStore.clearFile()" class="btn-secondary">
|
663 |
-
Changer de fichier
|
664 |
-
</button>
|
665 |
</div>
|
666 |
</div>
|
667 |
</section>
|
@@ -1142,6 +1214,36 @@ const onChartError = (error) => {
|
|
1142 |
font-weight: 600;
|
1143 |
}
|
1144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1145 |
.drop-zone {
|
1146 |
border: 3px dashed #555;
|
1147 |
border-radius: 12px;
|
@@ -1172,13 +1274,111 @@ const onChartError = (error) => {
|
|
1172 |
font-weight: 300;
|
1173 |
}
|
1174 |
|
1175 |
-
.drop-content
|
1176 |
font-size: 1.25rem;
|
1177 |
color: white;
|
1178 |
margin-bottom: 1rem;
|
1179 |
font-weight: 600;
|
1180 |
}
|
1181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1182 |
.or-text {
|
1183 |
color: #888;
|
1184 |
margin: 1.5rem 0;
|
@@ -1232,7 +1432,7 @@ const onChartError = (error) => {
|
|
1232 |
text-align: center;
|
1233 |
}
|
1234 |
|
1235 |
-
.file-info
|
1236 |
color: var(--color-primary);
|
1237 |
margin-bottom: 1.5rem;
|
1238 |
font-weight: 600;
|
|
|
7 |
import CalibrationArea from '@/components/CalibrationArea.vue'
|
8 |
import FootballField from '@/components/FootballField.vue'
|
9 |
import PlotlyChart from '@/components/PlotlyChart.vue'
|
10 |
+
import fieldTestImage from '@/assets/field_test_thumb.jpg'
|
11 |
|
12 |
const router = useRouter()
|
13 |
const calibrationStore = useCalibrationStore()
|
|
|
25 |
|
26 |
// États locaux
|
27 |
const dragActive = ref(false)
|
28 |
+
const selectedTestImage = ref(null)
|
29 |
+
|
30 |
+
// Image de test
|
31 |
+
const testImage = ref({
|
32 |
+
id: 'field1',
|
33 |
+
name: 'Terrain de test',
|
34 |
+
description: 'Image d\'exemple pour tester l\'analyse',
|
35 |
+
thumbnail: fieldTestImage,
|
36 |
+
fullUrl: fieldTestImage
|
37 |
+
})
|
38 |
|
39 |
// États pour la vue manuelle
|
40 |
const thumbnail = ref(null)
|
|
|
148 |
calibrationStore.setMode(mode)
|
149 |
}
|
150 |
|
151 |
+
const selectTestImage = async (testImage) => {
|
152 |
+
try {
|
153 |
+
selectedTestImage.value = testImage.id
|
154 |
+
|
155 |
+
// Créer un objet File à partir de l'URL de l'image de test
|
156 |
+
const response = await fetch(testImage.fullUrl)
|
157 |
+
const blob = await response.blob()
|
158 |
+
const file = new File([blob], `${testImage.name}.jpg`, { type: 'image/jpeg' })
|
159 |
+
|
160 |
+
uploadStore.setFile(file)
|
161 |
+
|
162 |
+
if (calibrationStore.isManualMode) {
|
163 |
+
// En mode manuel avec image, charger la vue manuelle intégrée
|
164 |
+
await loadThumbnail()
|
165 |
+
await nextTick()
|
166 |
+
scrollToSection(manualSection.value)
|
167 |
+
} else if (calibrationStore.isAutoMode) {
|
168 |
+
// En mode auto, lancement automatique pour les images
|
169 |
+
startProcessing()
|
170 |
+
}
|
171 |
+
} catch (error) {
|
172 |
+
console.error('Erreur lors du chargement de l\'image de test:', error)
|
173 |
+
// Fallback: utiliser l'URL directement pour la preview
|
174 |
+
uploadStore.filePreview = testImage.fullUrl
|
175 |
+
uploadStore.selectedFile = { name: `${testImage.name}.jpg`, size: 0 }
|
176 |
+
uploadStore.fileType = 'image'
|
177 |
+
}
|
178 |
+
}
|
179 |
+
|
180 |
const resetToStart = () => {
|
181 |
// Réinitialiser tous les stores
|
182 |
calibrationStore.reset()
|
|
|
379 |
}
|
380 |
|
381 |
const exportResults = () => {
|
382 |
+
// Encapsuler les données de calibration dans une clé "calibration"
|
383 |
+
const data = {
|
384 |
+
calibration: calibrationStore.results
|
385 |
+
}
|
386 |
+
|
387 |
+
// Générer le nom de fichier basé sur le fichier original
|
388 |
+
let filename = 'football_vision_config.json'
|
389 |
+
if (uploadStore.selectedFile && uploadStore.selectedFile.name) {
|
390 |
+
// Enlever l'extension du fichier original et ajouter .json
|
391 |
+
const originalName = uploadStore.selectedFile.name
|
392 |
+
const nameWithoutExtension = originalName.substring(0, originalName.lastIndexOf('.')) || originalName
|
393 |
+
filename = `${nameWithoutExtension}_config.json`
|
394 |
+
}
|
395 |
|
396 |
const content = JSON.stringify(data, null, 2)
|
397 |
|
|
|
654 |
</div>
|
655 |
</section>
|
656 |
|
657 |
+
<!-- Section 2: Upload de fichier -->
|
658 |
<section v-if="showUpload" ref="uploadSection" class="section upload-section">
|
659 |
+
<div class="upload-grid">
|
660 |
+
<!-- Côté gauche: Upload de fichier -->
|
661 |
+
<div class="upload-left">
|
662 |
+
<div
|
663 |
+
class="drop-zone"
|
664 |
+
:class="{
|
665 |
+
active: dragActive,
|
666 |
+
'has-file': uploadStore.isFileSelected,
|
667 |
+
'processing': uploadStore.isUploading
|
668 |
+
}"
|
669 |
+
@drop="handleDrop"
|
670 |
+
@dragover="handleDragOver"
|
671 |
+
@dragleave="handleDragLeave"
|
672 |
+
>
|
673 |
+
<div v-if="!uploadStore.isFileSelected" class="drop-content">
|
674 |
+
<div class="upload-icon">+</div>
|
675 |
+
<h4>Glissez-déposez votre fichier ici</h4>
|
676 |
+
<p class="or-text">ou</p>
|
677 |
+
<label class="file-input-label">
|
678 |
+
<input
|
679 |
+
type="file"
|
680 |
+
accept="image/*,video/*"
|
681 |
+
@change="handleFileSelect"
|
682 |
+
hidden
|
683 |
+
>
|
684 |
+
Choisir un fichier
|
685 |
+
</label>
|
686 |
+
</div>
|
687 |
+
|
688 |
+
<div v-else class="file-preview">
|
689 |
+
<div class="file-info">
|
690 |
+
<h4>Fichier sélectionné</h4>
|
691 |
+
<p class="file-name">{{ uploadStore.selectedFile.name }}</p>
|
692 |
+
<p class="file-details">
|
693 |
+
<span>Type: {{ uploadStore.fileType === 'image' ? 'Image' : 'Vidéo' }}</span>
|
694 |
+
<span>Taille: {{ Math.round(uploadStore.selectedFile.size / 1024) }} KB</span>
|
695 |
+
</p>
|
696 |
+
</div>
|
697 |
+
|
698 |
+
<div class="preview" v-if="uploadStore.filePreview">
|
699 |
+
<img
|
700 |
+
v-if="uploadStore.isImage"
|
701 |
+
:src="uploadStore.filePreview"
|
702 |
+
alt="Preview"
|
703 |
+
class="preview-media"
|
704 |
+
>
|
705 |
+
<video
|
706 |
+
v-else
|
707 |
+
:src="uploadStore.filePreview"
|
708 |
+
controls
|
709 |
+
class="preview-media"
|
710 |
+
>
|
711 |
+
</video>
|
712 |
+
</div>
|
713 |
+
|
714 |
+
<button @click="uploadStore.clearFile()" class="btn-secondary">
|
715 |
+
Changer de fichier
|
716 |
+
</button>
|
717 |
+
</div>
|
718 |
+
</div>
|
719 |
</div>
|
720 |
|
721 |
+
<!-- Côté droit: Images de test -->
|
722 |
+
<div class="upload-right">
|
723 |
+
<div class="test-image-container">
|
724 |
+
<div
|
725 |
+
class="test-image-card single"
|
726 |
+
@click="selectTestImage(testImage)"
|
727 |
+
:class="{ selected: selectedTestImage === testImage.id }"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
728 |
>
|
729 |
+
<img :src="testImage.thumbnail" :alt="testImage.name" class="test-image-thumb">
|
730 |
+
<div class="test-image-overlay">
|
731 |
+
<div class="overlay-content">
|
732 |
+
<p>Vue Drone</p>
|
733 |
+
</div>
|
734 |
+
</div>
|
735 |
+
</div>
|
736 |
</div>
|
|
|
|
|
|
|
|
|
737 |
</div>
|
738 |
</div>
|
739 |
</section>
|
|
|
1214 |
font-weight: 600;
|
1215 |
}
|
1216 |
|
1217 |
+
/* Grid Upload */
|
1218 |
+
.upload-grid {
|
1219 |
+
display: grid;
|
1220 |
+
grid-template-columns: 1fr 1fr;
|
1221 |
+
gap: 3rem;
|
1222 |
+
width: 100%;
|
1223 |
+
max-width: 1200px;
|
1224 |
+
align-items: start;
|
1225 |
+
padding: 2rem;
|
1226 |
+
}
|
1227 |
+
|
1228 |
+
.upload-left, .upload-right {
|
1229 |
+
display: flex;
|
1230 |
+
flex-direction: column;
|
1231 |
+
height: 100%;
|
1232 |
+
}
|
1233 |
+
|
1234 |
+
.upload-right {
|
1235 |
+
justify-content: center;
|
1236 |
+
align-items: center;
|
1237 |
+
}
|
1238 |
+
|
1239 |
+
.upload-title {
|
1240 |
+
font-size: 1.25rem;
|
1241 |
+
font-weight: 600;
|
1242 |
+
color: white;
|
1243 |
+
margin-bottom: 2rem;
|
1244 |
+
text-align: center;
|
1245 |
+
}
|
1246 |
+
|
1247 |
.drop-zone {
|
1248 |
border: 3px dashed #555;
|
1249 |
border-radius: 12px;
|
|
|
1274 |
font-weight: 300;
|
1275 |
}
|
1276 |
|
1277 |
+
.drop-content h4 {
|
1278 |
font-size: 1.25rem;
|
1279 |
color: white;
|
1280 |
margin-bottom: 1rem;
|
1281 |
font-weight: 600;
|
1282 |
}
|
1283 |
|
1284 |
+
/* Image de test */
|
1285 |
+
.test-image-container {
|
1286 |
+
display: flex;
|
1287 |
+
flex-direction: column;
|
1288 |
+
align-items: center;
|
1289 |
+
justify-content: center;
|
1290 |
+
width: 100%;
|
1291 |
+
height: 100%;
|
1292 |
+
}
|
1293 |
+
|
1294 |
+
.test-image-card.single {
|
1295 |
+
background: var(--color-secondary-soft);
|
1296 |
+
border-radius: 12px;
|
1297 |
+
overflow: hidden;
|
1298 |
+
cursor: pointer;
|
1299 |
+
transition: all 0.3s ease;
|
1300 |
+
border: 2px solid transparent;
|
1301 |
+
position: relative;
|
1302 |
+
width: 100%;
|
1303 |
+
height: 100%;
|
1304 |
+
display: flex;
|
1305 |
+
}
|
1306 |
+
|
1307 |
+
.test-image-card:hover {
|
1308 |
+
transform: translateY(-4px);
|
1309 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
1310 |
+
border-color: rgba(255, 255, 255, 0.2);
|
1311 |
+
}
|
1312 |
+
|
1313 |
+
.test-image-card.selected {
|
1314 |
+
border-color: var(--color-primary);
|
1315 |
+
box-shadow: 0 4px 20px rgba(217, 255, 4, 0.3);
|
1316 |
+
}
|
1317 |
+
|
1318 |
+
.test-image-card.selected::after {
|
1319 |
+
content: '✓';
|
1320 |
+
position: absolute;
|
1321 |
+
top: 8px;
|
1322 |
+
right: 8px;
|
1323 |
+
background: var(--color-primary);
|
1324 |
+
color: var(--color-secondary);
|
1325 |
+
width: 24px;
|
1326 |
+
height: 24px;
|
1327 |
+
border-radius: 50%;
|
1328 |
+
display: flex;
|
1329 |
+
align-items: center;
|
1330 |
+
justify-content: center;
|
1331 |
+
font-weight: bold;
|
1332 |
+
font-size: 0.8rem;
|
1333 |
+
}
|
1334 |
+
|
1335 |
+
.test-image-thumb {
|
1336 |
+
width: 100%;
|
1337 |
+
height: 100%;
|
1338 |
+
object-fit: cover;
|
1339 |
+
display: block;
|
1340 |
+
}
|
1341 |
+
|
1342 |
+
.test-image-overlay {
|
1343 |
+
position: absolute;
|
1344 |
+
top: 0;
|
1345 |
+
left: 0;
|
1346 |
+
right: 0;
|
1347 |
+
bottom: 0;
|
1348 |
+
background: rgba(0, 0, 0, 0.7);
|
1349 |
+
display: flex;
|
1350 |
+
align-items: center;
|
1351 |
+
justify-content: center;
|
1352 |
+
opacity: 1;
|
1353 |
+
transition: opacity 0.3s ease;
|
1354 |
+
backdrop-filter: blur(2px);
|
1355 |
+
}
|
1356 |
+
|
1357 |
+
.test-image-card.single:hover .test-image-overlay {
|
1358 |
+
opacity: 0;
|
1359 |
+
}
|
1360 |
+
|
1361 |
+
.overlay-content {
|
1362 |
+
text-align: center;
|
1363 |
+
color: white;
|
1364 |
+
}
|
1365 |
+
|
1366 |
+
.overlay-content h4 {
|
1367 |
+
font-size: 1.25rem;
|
1368 |
+
font-weight: 600;
|
1369 |
+
margin: 0 0 0.5rem 0;
|
1370 |
+
color: var(--color-primary);
|
1371 |
+
}
|
1372 |
+
|
1373 |
+
.overlay-content p {
|
1374 |
+
font-size: 0.9rem;
|
1375 |
+
margin: 0;
|
1376 |
+
color: #e0e0e0;
|
1377 |
+
font-weight: 500;
|
1378 |
+
}
|
1379 |
+
|
1380 |
+
|
1381 |
+
|
1382 |
.or-text {
|
1383 |
color: #888;
|
1384 |
margin: 1.5rem 0;
|
|
|
1432 |
text-align: center;
|
1433 |
}
|
1434 |
|
1435 |
+
.file-info h4 {
|
1436 |
color: var(--color-primary);
|
1437 |
margin-bottom: 1.5rem;
|
1438 |
font-weight: 600;
|