diff options
Diffstat (limited to 'src/public/js/app.js')
| -rw-r--r-- | src/public/js/app.js | 200 |
1 files changed, 199 insertions, 1 deletions
diff --git a/src/public/js/app.js b/src/public/js/app.js index 86b3c33..a6bb9c5 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -1,3 +1,201 @@ document.addEventListener('DOMContentLoaded', function () { - // App initialization + var canvas = document.getElementById('preview-canvas'); + if (!canvas) return; + + var ctx = canvas.getContext('2d'); + var video = document.getElementById('webcam-video'); + var btnCapture = document.getElementById('btn-capture'); + var btnSave = document.getElementById('btn-save'); + var fileInput = document.getElementById('file-input'); + var scaleSlider = document.getElementById('overlay-scale'); + var tabWebcam = document.getElementById('tab-webcam'); + var tabUpload = document.getElementById('tab-upload'); + var webcamPanel = document.getElementById('webcam-panel'); + var uploadPanel = document.getElementById('upload-panel'); + var csrfToken = document.getElementById('csrf-token').value; + var overlayThumbs = document.querySelectorAll('.overlay-thumb'); + + // State + var baseImageData = null; // base64 data URL of the captured/uploaded image + var selectedOverlay = null; // filename of the selected overlay + var overlayImg = null; // loaded Image object for the selected overlay + var webcamStream = null; + + // -- Tabs -- + + tabWebcam.addEventListener('click', function () { + tabWebcam.classList.add('active'); + tabUpload.classList.remove('active'); + webcamPanel.hidden = false; + uploadPanel.hidden = true; + startWebcam(); + }); + + tabUpload.addEventListener('click', function () { + tabUpload.classList.add('active'); + tabWebcam.classList.remove('active'); + uploadPanel.hidden = false; + webcamPanel.hidden = true; + stopWebcam(); + }); + + // -- Webcam -- + + function startWebcam() { + if (webcamStream) return; + navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 640, facingMode: 'user' } }) + .then(function (stream) { + webcamStream = stream; + video.srcObject = stream; + }) + .catch(function () { + // Camera not available — switch to upload tab + tabUpload.click(); + }); + } + + function stopWebcam() { + if (webcamStream) { + webcamStream.getTracks().forEach(function (t) { t.stop(); }); + webcamStream = null; + video.srcObject = null; + } + } + + btnCapture.addEventListener('click', function () { + // Draw current video frame onto an offscreen canvas to get a data URL + var offscreen = document.createElement('canvas'); + offscreen.width = 640; + offscreen.height = 640; + var offCtx = offscreen.getContext('2d'); + // Center-crop the video frame into the square canvas + var vw = video.videoWidth; + var vh = video.videoHeight; + var crop = Math.min(vw, vh); + var sx = (vw - crop) / 2; + var sy = (vh - crop) / 2; + offCtx.drawImage(video, sx, sy, crop, crop, 0, 0, 640, 640); + baseImageData = offscreen.toDataURL('image/jpeg', 0.9); + renderPreview(); + }); + + // -- Upload -- + + fileInput.addEventListener('change', function () { + var file = fileInput.files[0]; + if (!file) return; + var reader = new FileReader(); + reader.onload = function (e) { + baseImageData = e.target.result; + renderPreview(); + }; + reader.readAsDataURL(file); + }); + + // -- Overlay selection -- + + overlayThumbs.forEach(function (thumb) { + thumb.addEventListener('click', function () { + // Deselect previous + overlayThumbs.forEach(function (t) { t.classList.remove('selected'); }); + thumb.classList.add('selected'); + selectedOverlay = thumb.dataset.filename; + + // Preload the full overlay image for canvas rendering + overlayImg = new Image(); + overlayImg.onload = renderPreview; + overlayImg.src = thumb.src; + }); + }); + + // -- Scale slider -- + + scaleSlider.addEventListener('input', renderPreview); + + // -- Preview -- + + function renderPreview() { + ctx.clearRect(0, 0, 640, 640); + // Fill with light gray so the canvas isn't transparent + ctx.fillStyle = '#eee'; + ctx.fillRect(0, 0, 640, 640); + + if (!baseImageData) { + ctx.fillStyle = '#999'; + ctx.font = '18px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Take a photo or upload an image', 320, 320); + updateSaveButton(); + return; + } + + var img = new Image(); + img.onload = function () { + // Center-crop into the 640x640 canvas + var sw = img.width; + var sh = img.height; + var crop = Math.min(sw, sh); + var sx = (sw - crop) / 2; + var sy = (sh - crop) / 2; + ctx.drawImage(img, sx, sy, crop, crop, 0, 0, 640, 640); + + // Draw overlay on top if one is selected + if (overlayImg && overlayImg.complete) { + var scale = scaleSlider.value / 100; + var ow = overlayImg.width * scale; + var oh = overlayImg.height * scale; + var ox = (640 - ow) / 2; + var oy = (640 - oh) / 2; + ctx.drawImage(overlayImg, ox, oy, ow, oh); + } + + updateSaveButton(); + }; + img.src = baseImageData; + } + + function updateSaveButton() { + // Both a base image and an overlay must be selected + btnSave.disabled = !(baseImageData && selectedOverlay); + } + + // -- Save -- + + btnSave.addEventListener('click', function () { + if (btnSave.disabled) return; + btnSave.disabled = true; + btnSave.textContent = 'Saving...'; + + var scale = scaleSlider.value / 100; + + fetch('/editor', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + image_data: baseImageData, + overlay: selectedOverlay, + overlay_scale: scale, + csrf_token: csrfToken + }) + }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (data.success) { + window.location.href = data.redirect; + } else { + alert(data.error || 'Something went wrong.'); + btnSave.disabled = false; + btnSave.textContent = 'Save post'; + } + }) + .catch(function () { + alert('Network error. Please try again.'); + btnSave.disabled = false; + btnSave.textContent = 'Save post'; + }); + }); + + // Start webcam by default + startWebcam(); + renderPreview(); }); |
