Enhance SIDAK with two-way KK-KTP linkage, scanner mobile optimization, NIK validation, and UI improvements
- Implement bidirectional KK-KTP linkage system (address-based & NIK-based) - Optimize scanner for mobile devices (touch slop, larger hit areas, modal locking) - Add NIK validation (16-digit numeric) with client-side feedback - Set default RT/RW values to '000' for both KK and KTP forms - Change 'Kpl Keluarga' label to 'Kepala Keluarga' - Improve scanner error messages and user feedback - Remove redundant 'Deteksi Dokumen' button - Add database schema updates and Docker support files
This commit is contained in:
@@ -20,11 +20,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<div>
|
||||
<button type="button" class="btn btn-secondary" id="btnScanRotateLeft" title="Putar Kiri (-90°)"><i class="fas fa-undo"></i></button>
|
||||
<button type="button" class="btn btn-secondary" id="btnScanRotateRight" title="Putar Kanan (+90°)"><i class="fas fa-redo"></i></button>
|
||||
<button type="button" class="btn btn-warning" id="btnScanReset"><i class="fas fa-sync"></i> Reset Sudut</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-secondary" id="btnScanRotateLeft" title="Putar Kiri (-90°)"><i class="fas fa-undo"></i></button>
|
||||
<button type="button" class="btn btn-secondary" id="btnScanRotateRight" title="Putar Kanan (+90°)"><i class="fas fa-redo"></i></button>
|
||||
<button type="button" class="btn btn-warning" id="btnScanReset"><i class="fas fa-sync"></i> Reset Sudut</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||
<button type="button" class="btn btn-primary" id="btnScanSave"><i class="fas fa-check"></i> Simpan Hasil Scan</button>
|
||||
@@ -34,6 +34,117 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Disable all transitions and hover effects for scanner modal */
|
||||
#modalScanner .card,
|
||||
#modalScanner .modal-content,
|
||||
#modalScanner .modal-body,
|
||||
#modalScanner .modal-header,
|
||||
#modalScanner .modal-footer,
|
||||
#modalScanner .modal-dialog,
|
||||
#modalScanner #scanner-container,
|
||||
#modalScanner #scanner-container * {
|
||||
transition: none !important;
|
||||
}
|
||||
#modalScanner .card:hover,
|
||||
#modalScanner .modal-content:hover,
|
||||
#modalScanner .modal-body:hover,
|
||||
#modalScanner .modal-header:hover,
|
||||
#modalScanner .modal-footer:hover,
|
||||
#modalScanner .modal-dialog:hover,
|
||||
#modalScanner #scanner-container:hover,
|
||||
#modalScanner #scanner-container *:hover {
|
||||
transform: none !important;
|
||||
}
|
||||
/* Ensure canvas is fully visible */
|
||||
#canvas-image {
|
||||
opacity: 1 !important;
|
||||
filter: none !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
#canvas-overlay {
|
||||
opacity: 1 !important;
|
||||
filter: none !important;
|
||||
}
|
||||
/* Prevent canvas from moving on hover */
|
||||
#canvas-image:hover,
|
||||
#canvas-overlay:hover {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Prevent modal dragging and improve touch handling on mobile */
|
||||
#modalScanner .modal-dialog,
|
||||
#modalScanner .modal-content,
|
||||
#modalScanner .modal-header,
|
||||
#modalScanner .modal-footer {
|
||||
touch-action: none !important; /* Prevent browser touch gestures (pan, zoom, swipe) */
|
||||
user-select: none !important; /* Prevent text selection during drag */
|
||||
-webkit-user-select: none !important;
|
||||
-webkit-touch-callout: none !important;
|
||||
}
|
||||
|
||||
/* Allow touch interaction only on canvas and buttons */
|
||||
#modalScanner .modal-body,
|
||||
#canvas-image,
|
||||
#canvas-overlay {
|
||||
touch-action: manipulation !important; /* Allow pinch-zoom and pan on canvas only */
|
||||
}
|
||||
|
||||
/* Prevent modal backdrop from responding to touch */
|
||||
.modal-backdrop {
|
||||
touch-action: none !important;
|
||||
}
|
||||
|
||||
/* Lock modal position on mobile */
|
||||
@media (max-width: 768px) {
|
||||
#modalScanner {
|
||||
padding-right: 0 !important; /* Prevent shift from scrollbar */
|
||||
}
|
||||
#modalScanner .modal-dialog {
|
||||
margin: 0 !important;
|
||||
max-height: 100vh !important;
|
||||
height: 100vh !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
transform: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
#modalScanner .modal-content {
|
||||
border-radius: 0 !important;
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
overflow: hidden !important;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
transform: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
#modalScanner .modal-body {
|
||||
height: calc(100vh - 120px) !important; /* Account for header and footer */
|
||||
overflow: hidden !important;
|
||||
}
|
||||
/* Prevent any modal movement */
|
||||
.modal-open #modalScanner {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
/* Larger touch target for close button on mobile */
|
||||
#modalScanner .modal-header .close {
|
||||
padding: 20px !important;
|
||||
font-size: 2rem !important;
|
||||
line-height: 1 !important;
|
||||
margin: -10px -10px -10px auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
// Scanner Variables
|
||||
@@ -43,19 +154,31 @@ window.addEventListener('load', function() {
|
||||
var ctxImg = canvasImage.getContext('2d');
|
||||
var ctxOver = canvasOverlay.getContext('2d');
|
||||
|
||||
var scanner = new jscanify();
|
||||
var scanner = null;
|
||||
try {
|
||||
scanner = new jscanify();
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize jscanify:', e);
|
||||
// scanner remains null
|
||||
}
|
||||
var originalImg = new Image();
|
||||
|
||||
// Corner Points (tl, tr, bl, br)
|
||||
var corners = [];
|
||||
var activePoint = null;
|
||||
var isDragging = false;
|
||||
// Corner Points (tl, tr, bl, br)
|
||||
var corners = [];
|
||||
var activePoint = null;
|
||||
var isDragging = false;
|
||||
var touchStartPos = null;
|
||||
var isTouchInteraction = false;
|
||||
var touchOffset = null;
|
||||
|
||||
// Config
|
||||
const POINT_RADIUS = 15;
|
||||
const POINT_COLOR = '#007bff';
|
||||
const LINE_COLOR = '#00ff00';
|
||||
const LINE_WIDTH = 3;
|
||||
// Config
|
||||
const POINT_RADIUS = 15;
|
||||
const POINT_COLOR = '#007bff';
|
||||
const LINE_COLOR = '#00ff00';
|
||||
const LINE_WIDTH = 3;
|
||||
const TOUCH_RADIUS_MULTIPLIER = 2.5; // Larger hit area for touch devices
|
||||
const TOUCH_SLOP = 5; // pixels threshold before dragging starts
|
||||
const IS_TOUCH_DEVICE = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// --- Public Function to Open Scanner ---
|
||||
// --- Public Function to Open Scanner ---
|
||||
@@ -91,75 +214,139 @@ window.addEventListener('load', function() {
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
// 2. Show Modal & Listen
|
||||
scannerModal.off('shown.bs.modal'); // Remove old listeners
|
||||
scannerModal.on('shown.bs.modal', function() {
|
||||
modalShown = true;
|
||||
checkReady();
|
||||
});
|
||||
// 2. Show Modal & Listen
|
||||
scannerModal.off('shown.bs.modal'); // Remove old listeners
|
||||
scannerModal.on('shown.bs.modal', function() {
|
||||
modalShown = true;
|
||||
checkReady();
|
||||
|
||||
// Prevent body scrolling on mobile
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.width = '100%';
|
||||
|
||||
// Prevent modal dragging on mobile
|
||||
var modalDialog = document.querySelector('#modalScanner .modal-dialog');
|
||||
var modalContent = document.querySelector('#modalScanner .modal-content');
|
||||
var modalHeader = document.querySelector('#modalScanner .modal-header');
|
||||
var modalFooter = document.querySelector('#modalScanner .modal-footer');
|
||||
|
||||
var preventTouch = function(e) {
|
||||
// Only prevent if not clicking a button
|
||||
if (e.target.tagName !== 'BUTTON' && !e.target.closest('button')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
if (modalDialog) {
|
||||
modalDialog.addEventListener('touchstart', preventTouch, { passive: false });
|
||||
modalDialog.addEventListener('touchmove', preventTouch, { passive: false });
|
||||
}
|
||||
if (modalContent) {
|
||||
modalContent.addEventListener('touchstart', preventTouch, { passive: false });
|
||||
modalContent.addEventListener('touchmove', preventTouch, { passive: false });
|
||||
}
|
||||
if (modalHeader) {
|
||||
modalHeader.addEventListener('touchstart', preventTouch, { passive: false });
|
||||
modalHeader.addEventListener('touchmove', preventTouch, { passive: false });
|
||||
}
|
||||
if (modalFooter) {
|
||||
modalFooter.addEventListener('touchstart', preventTouch, { passive: false });
|
||||
modalFooter.addEventListener('touchmove', preventTouch, { passive: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Restore scrolling when modal is hidden
|
||||
scannerModal.on('hidden.bs.modal', function() {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
});
|
||||
|
||||
scannerModal.modal('show');
|
||||
};
|
||||
|
||||
function initScanner() {
|
||||
// Resize Canvas to fit screen but keep aspect ratio
|
||||
var maxWidth = $('#modalScanner .modal-body').width() - 20;
|
||||
var maxHeight = $('#modalScanner .modal-body').height() - 20;
|
||||
|
||||
var scale = Math.min(maxWidth / originalImg.width, maxHeight / originalImg.height);
|
||||
var w = originalImg.width * scale;
|
||||
var h = originalImg.height * scale;
|
||||
|
||||
canvasImage.width = w;
|
||||
canvasImage.height = h;
|
||||
canvasOverlay.width = w;
|
||||
canvasOverlay.height = h;
|
||||
|
||||
// Resize container
|
||||
$('#scanner-container').css({ width: w, height: h, marginTop: '10px' });
|
||||
|
||||
// Draw Image
|
||||
ctxImg.drawImage(originalImg, 0, 0, w, h);
|
||||
|
||||
// Detect Contour using jscanify
|
||||
try {
|
||||
// jscanify expects an image element, we can pass originalImg but we need to map coordinates
|
||||
// Wait, jscanify uses OpenCV which might not be ready.
|
||||
if (typeof cv !== 'undefined' && cv.Mat) {
|
||||
// We need to work on the original image for detection, then scale points
|
||||
var contour = scanner.findPaper(originalImg);
|
||||
// contour returns { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner } each {x, y}
|
||||
|
||||
if (contour) {
|
||||
corners = [
|
||||
{ x: contour.topLeftCorner.x * scale, y: contour.topLeftCorner.y * scale },
|
||||
{ x: contour.topRightCorner.x * scale, y: contour.topRightCorner.y * scale },
|
||||
{ x: contour.bottomRightCorner.x * scale, y: contour.bottomRightCorner.y * scale }, // Order: tr -> br -> bl ?? No, usually tl, tr, br, bl order for polygon drawing
|
||||
{ x: contour.bottomLeftCorner.x * scale, y: contour.bottomLeftCorner.y * scale }
|
||||
];
|
||||
// Reorder primarily for logic: TL, TR, BR, BL
|
||||
corners = [
|
||||
{ x: contour.topLeftCorner.x * scale, y: contour.topLeftCorner.y * scale },
|
||||
{ x: contour.topRightCorner.x * scale, y: contour.topRightCorner.y * scale },
|
||||
{ x: contour.bottomRightCorner.x * scale, y: contour.bottomRightCorner.y * scale },
|
||||
{ x: contour.bottomLeftCorner.x * scale, y: contour.bottomLeftCorner.y * scale }
|
||||
];
|
||||
function detectDocument(scale, w, h, showAlert = false) {
|
||||
// Detect Contour using jscanify
|
||||
try {
|
||||
// jscanify expects an image element, we can pass originalImg but we need to map coordinates
|
||||
// Wait, jscanify uses OpenCV which might not be ready.
|
||||
if (typeof cv !== 'undefined' && cv.Mat) {
|
||||
// We need to work on the original image for detection, then scale points
|
||||
var contour = scanner ? scanner.findPaper(originalImg) : null;
|
||||
// contour returns { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner } each {x, y}
|
||||
|
||||
if (contour) {
|
||||
// Order: TL, TR, BR, BL (clockwise)
|
||||
corners = [
|
||||
{ x: contour.topLeftCorner.x * scale, y: contour.topLeftCorner.y * scale },
|
||||
{ x: contour.topRightCorner.x * scale, y: contour.topRightCorner.y * scale },
|
||||
{ x: contour.bottomRightCorner.x * scale, y: contour.bottomRightCorner.y * scale },
|
||||
{ x: contour.bottomLeftCorner.x * scale, y: contour.bottomLeftCorner.y * scale }
|
||||
];
|
||||
|
||||
} else {
|
||||
defaultCorners(w, h);
|
||||
}
|
||||
} else {
|
||||
console.warn("OpenCV not ready yet");
|
||||
defaultCorners(w, h);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("Scanner Error:", e);
|
||||
defaultCorners(w, h); // Fallback
|
||||
}
|
||||
|
||||
$('#scanner-loading').hide();
|
||||
drawOverlay();
|
||||
}
|
||||
} else {
|
||||
defaultCorners(w, h);
|
||||
if (showAlert) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Deteksi Gagal',
|
||||
text: 'Dokumen tidak terdeteksi. Silakan atur sudut secara manual.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("OpenCV not ready yet");
|
||||
defaultCorners(w, h);
|
||||
if (showAlert) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Scanner Tidak Siap',
|
||||
text: 'OpenCV belum siap. Silakan atur sudut secara manual.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("Scanner Error:", e);
|
||||
defaultCorners(w, h); // Fallback
|
||||
if (showAlert) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error Scanner',
|
||||
text: 'Terjadi kesalahan saat mendeteksi dokumen: ' + e.message,
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
}
|
||||
drawOverlay();
|
||||
}
|
||||
|
||||
function initScanner() {
|
||||
// Resize Canvas to fit screen but keep aspect ratio
|
||||
var maxWidth = $('#modalScanner .modal-body').width() - 20;
|
||||
var maxHeight = $('#modalScanner .modal-body').height() - 20;
|
||||
|
||||
var scale = Math.min(maxWidth / originalImg.width, maxHeight / originalImg.height);
|
||||
var w = originalImg.width * scale;
|
||||
var h = originalImg.height * scale;
|
||||
|
||||
canvasImage.width = w;
|
||||
canvasImage.height = h;
|
||||
canvasOverlay.width = w;
|
||||
canvasOverlay.height = h;
|
||||
|
||||
// Resize container
|
||||
$('#scanner-container').css({ width: w, height: h, marginTop: '10px' });
|
||||
|
||||
// Draw Image
|
||||
ctxImg.drawImage(originalImg, 0, 0, w, h);
|
||||
|
||||
detectDocument(scale, w, h, false);
|
||||
$('#scanner-loading').hide();
|
||||
}
|
||||
|
||||
function defaultCorners(w, h) {
|
||||
// Default 20% margin
|
||||
@@ -211,44 +398,115 @@ window.addEventListener('load', function() {
|
||||
};
|
||||
}
|
||||
|
||||
function isInside(pos, point) {
|
||||
var dx = pos.x - point.x;
|
||||
var dy = pos.y - point.y;
|
||||
return dx * dx + dy * dy <= POINT_RADIUS * POINT_RADIUS * 2; // Bigger hit area
|
||||
}
|
||||
function isInside(pos, point) {
|
||||
var dx = pos.x - point.x;
|
||||
var dy = pos.y - point.y;
|
||||
return dx * dx + dy * dy <= POINT_RADIUS * POINT_RADIUS * 2; // Bigger hit area
|
||||
}
|
||||
|
||||
function getClosestCorner(pos, isTouch) {
|
||||
var closestIdx = -1;
|
||||
var closestDist = Infinity;
|
||||
var radius = POINT_RADIUS * (isTouch ? TOUCH_RADIUS_MULTIPLIER : 1.5); // Larger radius for touch
|
||||
var radiusSq = radius * radius;
|
||||
|
||||
corners.forEach((p, i) => {
|
||||
var dx = pos.x - p.x;
|
||||
var dy = pos.y - p.y;
|
||||
var distSq = dx * dx + dy * dy;
|
||||
if (distSq < radiusSq && distSq < closestDist) {
|
||||
closestDist = distSq;
|
||||
closestIdx = i;
|
||||
}
|
||||
});
|
||||
return closestIdx;
|
||||
}
|
||||
|
||||
canvasOverlay.addEventListener('mousedown', function(e) { handleStart(getMousePos(e)); });
|
||||
canvasOverlay.addEventListener('touchstart', function(e) { handleStart(getMousePos(e)); e.preventDefault(); }, {passive: false});
|
||||
canvasOverlay.addEventListener('mousedown', function(e) {
|
||||
handleStart(getMousePos(e), false);
|
||||
e.stopPropagation();
|
||||
});
|
||||
canvasOverlay.addEventListener('touchstart', function(e) {
|
||||
var pos = getMousePos(e);
|
||||
handleStart(pos, true);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, {passive: false});
|
||||
|
||||
window.addEventListener('mousemove', function(e) { if(isDragging) handleMove(getMousePos(e)); }); // Window to catch drag out
|
||||
canvasOverlay.addEventListener('touchmove', function(e) { if(isDragging) handleMove(getMousePos(e)); e.preventDefault(); }, {passive: false});
|
||||
window.addEventListener('mousemove', function(e) { if(isDragging) handleMove(getMousePos(e)); }); // Window to catch drag out
|
||||
canvasOverlay.addEventListener('touchmove', function(e) {
|
||||
if(isDragging) {
|
||||
handleMove(getMousePos(e));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, {passive: false});
|
||||
|
||||
window.addEventListener('mouseup', function() { handleEnd(); });
|
||||
window.addEventListener('touchend', function() { handleEnd(); });
|
||||
|
||||
function handleStart(pos) {
|
||||
activePoint = null;
|
||||
corners.forEach((p, i) => {
|
||||
if (isInside(pos, p)) {
|
||||
activePoint = i;
|
||||
isDragging = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleStart(pos, isTouch = false) {
|
||||
activePoint = getClosestCorner(pos, isTouch);
|
||||
if (activePoint !== -1) {
|
||||
isTouchInteraction = isTouch;
|
||||
touchOffset = { x: pos.x - corners[activePoint].x, y: pos.y - corners[activePoint].y };
|
||||
if (isTouch) {
|
||||
touchStartPos = pos;
|
||||
// Start dragging immediately but handle slop in handleMove
|
||||
isDragging = true;
|
||||
} else {
|
||||
isDragging = true;
|
||||
touchStartPos = null;
|
||||
}
|
||||
} else {
|
||||
activePoint = null;
|
||||
isDragging = false;
|
||||
isTouchInteraction = false;
|
||||
touchStartPos = null;
|
||||
touchOffset = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleMove(pos) {
|
||||
if (activePoint !== null) {
|
||||
// Constrain to canvas?? Optional but good
|
||||
corners[activePoint].x = pos.x;
|
||||
corners[activePoint].y = pos.y;
|
||||
drawOverlay();
|
||||
}
|
||||
}
|
||||
function handleMove(pos) {
|
||||
if (activePoint !== null) {
|
||||
// Touch slop detection
|
||||
if (isTouchInteraction && touchStartPos) {
|
||||
var dx = pos.x - touchStartPos.x;
|
||||
var dy = pos.y - touchStartPos.y;
|
||||
var distSq = dx * dx + dy * dy;
|
||||
if (distSq < TOUCH_SLOP * TOUCH_SLOP) {
|
||||
return; // Ignore small movements until slop exceeded
|
||||
}
|
||||
// Slop exceeded, clear touchStartPos so we don't check again
|
||||
touchStartPos = null;
|
||||
}
|
||||
|
||||
// Apply offset to maintain relative position
|
||||
if (touchOffset) {
|
||||
corners[activePoint].x = pos.x - touchOffset.x;
|
||||
corners[activePoint].y = pos.y - touchOffset.y;
|
||||
} else {
|
||||
corners[activePoint].x = pos.x;
|
||||
corners[activePoint].y = pos.y;
|
||||
}
|
||||
|
||||
// Optional: constrain to canvas bounds
|
||||
var w = canvasOverlay.width;
|
||||
var h = canvasOverlay.height;
|
||||
corners[activePoint].x = Math.max(0, Math.min(w, corners[activePoint].x));
|
||||
corners[activePoint].y = Math.max(0, Math.min(h, corners[activePoint].y));
|
||||
|
||||
drawOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnd() {
|
||||
isDragging = false;
|
||||
activePoint = null;
|
||||
}
|
||||
function handleEnd() {
|
||||
isDragging = false;
|
||||
activePoint = null;
|
||||
isTouchInteraction = false;
|
||||
touchStartPos = null;
|
||||
touchOffset = null;
|
||||
}
|
||||
|
||||
// --- Rotate Functions ---
|
||||
function rotateImage(degree) {
|
||||
@@ -271,8 +529,8 @@ window.addEventListener('load', function() {
|
||||
originalImg.src = rotatedUrl;
|
||||
}
|
||||
|
||||
$('#btnScanRotateLeft').click(function() { rotateImage(-90); });
|
||||
$('#btnScanRotateRight').click(function() { rotateImage(90); });
|
||||
$('#btnScanRotateLeft').click(function() { rotateImage(-90); });
|
||||
$('#btnScanRotateRight').click(function() { rotateImage(90); });
|
||||
|
||||
// --- Reset Button ---
|
||||
$('#btnScanReset').click(function() {
|
||||
@@ -309,7 +567,13 @@ window.addEventListener('load', function() {
|
||||
};
|
||||
|
||||
// 3. Extract with dynamic dimensions
|
||||
var resultCanvas = scanner.extractPaper(originalImg, outputWidth, outputHeight, extractPoints);
|
||||
var resultCanvas = null;
|
||||
if (scanner && scanner.extractPaper) {
|
||||
resultCanvas = scanner.extractPaper(originalImg, outputWidth, outputHeight, extractPoints);
|
||||
} else {
|
||||
alert("Scanner library not loaded. Please refresh the page.");
|
||||
return;
|
||||
}
|
||||
var base64 = resultCanvas.toDataURL('image/jpeg');
|
||||
|
||||
if (window.handleScannerResult) {
|
||||
|
||||
Reference in New Issue
Block a user