diff --git a/src/views/CameraConfiguration.vue b/src/views/CameraConfiguration.vue
index 0b77be1..bd395db 100644
--- a/src/views/CameraConfiguration.vue
+++ b/src/views/CameraConfiguration.vue
@@ -2,6 +2,34 @@
+
+
+
+
+ mdi-fullscreen
+ Go fullscreen
+
+
+
+ Please run calibration in fullscreen so the app can measure screen borders correctly.
+
+
+ {{ fullscreenError }}
+
+
+
+
+ mdi-fullscreen
+ Enter Fullscreen
+
+
+
+
+
@@ -174,7 +202,7 @@
color="#FF425A"
dark
:disabled="!isCameraOn"
- @click="goToCalibRecord()"
+ @click="startCalibration"
>
mdi-play
Start Calibration
@@ -213,6 +241,8 @@ export default {
selectedMediaDevice: null,
setupStep: 1,
showCameraModal: false,
+ showFullscreenModal: false,
+ fullscreenError: "",
};
},
computed: {
@@ -253,6 +283,44 @@ export default {
}
},
methods: {
+ isFullscreen() {
+ return !!(
+ document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.mozFullScreenElement ||
+ document.msFullscreenElement
+ );
+ },
+ requestFullscreen(element) {
+ const el = element || document.documentElement;
+ if (el.requestFullscreen) return el.requestFullscreen();
+ if (el.mozRequestFullScreen) return el.mozRequestFullScreen();
+ if (el.webkitRequestFullscreen) return el.webkitRequestFullscreen();
+ if (el.msRequestFullscreen) return el.msRequestFullscreen();
+ return Promise.reject(new Error("Fullscreen API not supported"));
+ },
+ async tryEnterFullscreen() {
+ this.fullscreenError = "";
+ try {
+ if (!this.isFullscreen()) {
+ await this.requestFullscreen(document.documentElement);
+ }
+ } catch (e) {
+ this.fullscreenError =
+ "Your browser blocked fullscreen. Please allow fullscreen and try again.";
+ }
+ if (this.isFullscreen()) {
+ this.showFullscreenModal = false;
+ }
+ },
+ async startCalibration() {
+ await this.tryEnterFullscreen();
+ if (!this.isFullscreen()) {
+ this.showFullscreenModal = true;
+ return;
+ }
+ this.goToCalibRecord();
+ },
startCameraSetup() {
this.setupStep = 2;
this.setupCamera();
@@ -433,45 +501,6 @@ export default {
);
ctx.stroke();
},
- fullScreen() {
- var element = document.documentElement;
- if (element.requestFullscreen) {
- if (!document.fullscreenElement) {
- element.requestFullscreen().catch((err) => {
- console.error("Erro ao entrar em tela cheia:", err);
- });
- } else {
- document.exitFullscreen();
- }
- } else if (element.mozRequestFullScreen) {
- // Para o Firefox
- if (!document.mozFullScreenElement) {
- element.mozRequestFullScreen().catch((err) => {
- console.error("Erro ao entrar em tela cheia:", err);
- });
- } else {
- document.mozCancelFullScreen();
- }
- } else if (element.webkitRequestFullscreen) {
- // Para o Chrome, Safari e Opera
- if (!document.webkitFullscreenElement) {
- element.webkitRequestFullscreen().catch((err) => {
- console.error("Erro ao entrar em tela cheia:", err);
- });
- } else {
- document.webkitExitFullscreen();
- }
- } else if (element.msRequestFullscreen) {
- // Para o Internet Explorer e Microsoft Edge
- if (!document.msFullscreenElement) {
- element.msRequestFullscreen().catch((err) => {
- console.error("Erro ao entrar em tela cheia:", err);
- });
- } else {
- document.msExitFullscreen();
- }
- }
- },
goToCalibRecord() {
this.webcamStream.getTracks().forEach((track) => {
track.stop();
diff --git a/src/views/DoubleCalibrationRecord.vue b/src/views/DoubleCalibrationRecord.vue
index 0885bba..c665dcb 100644
--- a/src/views/DoubleCalibrationRecord.vue
+++ b/src/views/DoubleCalibrationRecord.vue
@@ -1,5 +1,42 @@
+
+
+
+
+ mdi-fullscreen
+ Go fullscreen
+
+
+ Please keep fullscreen on during calibration so the app can measure screen borders correctly.
+
+
+
+ Enter fullscreen
+
+
+
+
+
+
+
+
+
+ mdi-lock
+ Calibration in progress
+
+
+ Navigation is disabled during calibration to ensure accurate border
+ positioning. Please finish calibration first.
+
+
+
+ OK
+
+
+
+
+
@@ -194,7 +231,15 @@
Processing your calibration data...
-
+
mdi-check-bold
Finish
@@ -242,7 +287,11 @@ export default {
stepperStep: 1,
showStepper: true,
calibrationStarted: false,
- loadingConfig: false
+ loadingConfig: false,
+
+ fullscreenRequiredDialog: false,
+ navigationBlockedDialog: false,
+ finishingCalibration: false,
};
},
computed: {
@@ -311,7 +360,91 @@ export default {
console.log("UsedPattern inteiro", this.usedPattern);
},
+ mounted() {
+ document.addEventListener("fullscreenchange", this.onFullscreenChange);
+ document.addEventListener("webkitfullscreenchange", this.onFullscreenChange);
+ window.addEventListener("beforeunload", this.onBeforeUnload);
+
+ this.applyCalibrationScrollLock();
+
+ if (!this.isFullscreen()) {
+ this.fullscreenRequiredDialog = true;
+ }
+ },
+ beforeDestroy() {
+ document.removeEventListener("fullscreenchange", this.onFullscreenChange);
+ document.removeEventListener("webkitfullscreenchange", this.onFullscreenChange);
+ window.removeEventListener("beforeunload", this.onBeforeUnload);
+
+ this.removeCalibrationScrollLock();
+ },
+ beforeRouteLeave(to, from, next) {
+ if (this.calibrationStarted && !this.calibFinished && !this.finishingCalibration) {
+ this.navigationBlockedDialog = true;
+ next(false);
+ return;
+ }
+ next();
+ },
methods: {
+ applyCalibrationScrollLock() {
+ const body = document.body;
+ const html = document.documentElement;
+ if (!body || !html) return;
+
+ body.style.overflow = "hidden";
+ html.style.overflow = "hidden";
+ body.style.overscrollBehavior = "none";
+ html.style.overscrollBehavior = "none";
+ },
+ removeCalibrationScrollLock() {
+ const body = document.body;
+ const html = document.documentElement;
+ if (!body || !html) return;
+
+ body.style.overflow = "";
+ html.style.overflow = "";
+ body.style.overscrollBehavior = "";
+ html.style.overscrollBehavior = "";
+ },
+ isFullscreen() {
+ return !!(
+ document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.mozFullScreenElement ||
+ document.msFullscreenElement
+ );
+ },
+ requestFullscreen(element) {
+ const el = element || document.documentElement;
+ if (el.requestFullscreen) return el.requestFullscreen();
+ if (el.mozRequestFullScreen) return el.mozRequestFullScreen();
+ if (el.webkitRequestFullscreen) return el.webkitRequestFullscreen();
+ if (el.msRequestFullscreen) return el.msRequestFullscreen();
+ return Promise.reject(new Error("Fullscreen API not supported"));
+ },
+ async requestFullscreenAndClose() {
+ try {
+ await this.requestFullscreen(document.documentElement);
+ } catch (e) {
+ // Keep dialog open; browser will show its own UI hints if blocked.
+ }
+ if (this.isFullscreen()) {
+ this.fullscreenRequiredDialog = false;
+ }
+ },
+ onFullscreenChange() {
+ if (this.calibrationStarted && !this.calibFinished && !this.isFullscreen()) {
+ this.fullscreenRequiredDialog = true;
+ this.showStepper = true;
+ }
+ },
+ onBeforeUnload(e) {
+ if (this.calibrationStarted && !this.calibFinished) {
+ e.preventDefault();
+ e.returnValue = "";
+ }
+ },
generateCalibrationPattern(pointCount, width, height, offset) {
const patterns = [];
@@ -415,10 +548,18 @@ export default {
},
startTraining() {
+ if (!this.isFullscreen()) {
+ this.fullscreenRequiredDialog = true;
+ return;
+ }
this.showStepper = false;
this.calibrationStarted = true;
},
startValidation() {
+ if (!this.isFullscreen()) {
+ this.fullscreenRequiredDialog = true;
+ return;
+ }
this.showStepper = false;
this.calibrationStarted = true;
},
@@ -432,10 +573,26 @@ export default {
}
if ((event.key === "s" || event.key === "S" || event.key === "Enter")) {
+ if (!th.isFullscreen()) {
+ th.fullscreenRequiredDialog = true;
+ th.showStepper = true;
+ return;
+ }
if (i < pattern.length) {
document.removeEventListener('keydown', keydownHandler)
th.isCollecting = true
- await th.extract(pattern[i], timeBetweenCaptures)
+ try {
+ await th.extract(pattern[i], timeBetweenCaptures)
+ } catch (e) {
+ th.isCollecting = false
+ if (e && e.message === "fullscreen_required") {
+ th.fullscreenRequiredDialog = true
+ th.showStepper = true
+ document.addEventListener('keydown', keydownHandler)
+ return
+ }
+ throw e
+ }
th.isCollecting = false
th.$store.commit('setIndex', i)
@@ -482,6 +639,9 @@ export default {
async extract(point, timeBetweenCaptures) {
point.data = [];
for (var a = 0; a < this.predByPointCount;) {
+ if (!this.isFullscreen()) {
+ throw new Error("fullscreen_required");
+ }
const prediction = await this.detectFace();
// 🛡️ DEFENSIVE GUARD: Check if face is detected
@@ -601,6 +761,10 @@ export default {
ctx.stroke();
},
async endCalib() {
+ if (this.finishingCalibration) return;
+ this.finishingCalibration = true;
+ this.calibrationStarted = false;
+
this.calibPredictionPoints.forEach(element => {
delete element.point_x;
delete element.point_y;
@@ -648,7 +812,8 @@ export default {
fromRuxailab: this.fromRuxailab,
})
- this.stopRecord()
+ await this.stopRecord()
+ this.calibFinished = true
this.$router.push(
`/postCalibration?redirectingToRuxailab=${this.fromRuxailab}`
@@ -759,8 +924,11 @@ export default {
return lastPrediction
},
- stopRecord() {
- this.recordWebCam.state != "inactive" ? this.stopWebCamCapture() : null;
+ async stopRecord() {
+ if (!this.recordWebCam) return;
+ if (this.recordWebCam.state != "inactive") {
+ await this.stopWebCamCapture();
+ }
},
async stopWebCamCapture() {
@@ -805,13 +973,6 @@ export default {