document.addEventListener('DOMContentLoaded', () => { // --- Core UI Elements --- const form = document.getElementById('generation-form'); const generateBtn = document.getElementById('generate-btn'); const placeholderText = document.getElementById('placeholder-text'); const loadingState = document.getElementById('loading-state'); const contentContainer = document.getElementById('content-container'); const actionButtons = document.getElementById('action-buttons'); const downloadBtn = document.getElementById('download-btn'); const editBtn = document.getElementById('edit-btn'); const providerBadge = document.getElementById('provider-badge'); const infoOverlay = document.getElementById('info-overlay'); const statusMessage = document.getElementById('status-message'); const uploadInput = document.getElementById('upload-image'); // --- Editor Elements --- const editorModalEl = document.getElementById('editorModal'); const editorModal = new bootstrap.Modal(editorModalEl); const editorLoading = document.getElementById('editor-loading'); const cropperImg = document.getElementById('cropper-image'); const fabricWrapper = document.getElementById('fabric-wrapper'); const transformBar = document.getElementById('transform-bar'); // Controls const filterRanges = document.querySelectorAll('.filter-range'); const brushToggle = document.getElementById('brush-toggle'); const brushColor = document.getElementById('brush-color'); const brushSize = document.getElementById('brush-size'); const textInput = document.getElementById('text-input'); const addTextBtn = document.getElementById('add-text-btn'); const textColor = document.getElementById('text-color'); const fontFamily = document.getElementById('font-family'); const stickerItems = document.querySelectorAll('.sticker-item'); const startCropBtn = document.getElementById('start-crop-btn'); const applyCropBtn = document.getElementById('apply-crop-btn'); const resetEditorBtn = document.getElementById('reset-editor'); const saveEditedBtn = document.getElementById('save-edited-btn'); const saveToGalleryBtn = document.getElementById('save-to-gallery-btn'); const aiEditPrompt = document.getElementById('ai-edit-prompt'); const applyAiMagicBtn = document.getElementById('apply-ai-magic'); const removeBgBtn = document.getElementById('remove-bg-btn'); const upscaleBtn = document.getElementById('upscale-btn'); // --- Editor State --- let canvas = null; let cropper = null; let fabricImage = null; // background image let currentResultUrl = ''; let originalPrompt = ''; let isDrawing = false; // --- Generation Logic --- form.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); originalPrompt = formData.get('prompt'); showLoading(); try { const response = await fetch('api/generate.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) renderResult(result); else alert('Ошибка: ' + (result.error || 'Сбой генерации')); } catch (error) { alert('Сетевая ошибка'); } finally { hideLoading(); } }); function showLoading() { placeholderText.classList.add('d-none'); contentContainer.classList.add('d-none'); actionButtons.classList.add('d-none'); infoOverlay.classList.add('d-none'); statusMessage.classList.add('d-none'); loadingState.classList.remove('d-none'); generateBtn.disabled = true; } function hideLoading() { loadingState.classList.add('d-none'); generateBtn.disabled = false; } function renderResult(result) { contentContainer.innerHTML = ''; contentContainer.classList.remove('d-none'); actionButtons.classList.remove('d-none'); infoOverlay.classList.remove('d-none'); currentResultUrl = result.url; providerBadge.textContent = result.provider; providerBadge.className = result.is_ai ? 'badge-nano bg-info' : 'badge-nano bg-warning'; if (result.type === 'photo') { const img = document.createElement('img'); img.src = result.url; img.className = 'img-fluid shadow-sm rounded mx-auto d-block'; img.style.maxHeight = '480px'; contentContainer.appendChild(img); editBtn.classList.remove('d-none'); } else { const video = document.createElement('video'); video.src = result.url; video.controls = true; video.className = 'rounded mx-auto d-block shadow-sm'; video.style.maxWidth = '100%'; video.style.maxHeight = '480px'; contentContainer.appendChild(video); editBtn.classList.add('d-none'); } } // --- Upload Handler --- uploadInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (f) => { originalPrompt = "Uploaded image"; openEditor(f.target.result); }; reader.readAsDataURL(file); }); // --- Editor Initialization --- function initFabric() { if (canvas) return; canvas = new fabric.Canvas('editor-canvas', { isDrawingMode: false, preserveObjectStacking: true }); // Handle object deletion window.addEventListener('keydown', (e) => { if (e.key === 'Delete' || e.key === 'Backspace') { deleteObject(); } }); } window.deleteObject = () => { const activeObjects = canvas.getActiveObjects(); if (activeObjects.length && !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) { canvas.remove(...activeObjects); canvas.discardActiveObject(); canvas.requestRenderAll(); } }; window.bringToFront = () => { const active = canvas.getActiveObject(); if (active) { active.bringToFront(); canvas.requestRenderAll(); } }; window.sendToBack = () => { const active = canvas.getActiveObject(); if (active) { // Keep background image at the very bottom active.sendToBack(); if (fabricImage) fabricImage.sendToBack(); canvas.requestRenderAll(); } }; function openEditor(url) { initFabric(); editorLoading.classList.remove('d-none'); editorLoading.classList.add('d-flex'); // Reset State canvas.clear(); isDrawing = false; brushToggle.classList.remove('active'); canvas.isDrawingMode = false; fabric.Image.fromURL(url, (img) => { fabricImage = img; const maxDimension = 1000; if (img.width > maxDimension || img.height > maxDimension) { const scale = maxDimension / Math.max(img.width, img.height); img.scale(scale); } canvas.setWidth(img.getScaledWidth()); canvas.setHeight(img.getScaledHeight()); canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); // Sync ranges filterRanges.forEach(r => { const f = r.dataset.filter; r.value = (f === 'brightness' || f === 'contrast' || f === 'saturate') ? 100 : 0; const vDisplay = document.getElementById(`val-${f}`); if (vDisplay) vDisplay.textContent = r.value; }); editorLoading.classList.add('d-none'); editorLoading.classList.remove('d-flex'); editorModal.show(); }, { crossOrigin: 'anonymous' }); } editBtn.addEventListener('click', () => currentResultUrl && openEditor(currentResultUrl)); downloadBtn.addEventListener('click', () => { if (!currentResultUrl) return; const a = document.createElement('a'); a.href = currentResultUrl; a.download = `nano_${Date.now()}.${currentResultUrl.includes('.mp4') ? 'mp4' : 'jpg'}`; a.click(); }); // --- Filter Logic --- filterRanges.forEach(range => { range.addEventListener('input', () => { const filterType = range.dataset.filter; const val = parseFloat(range.value); const vDisplay = document.getElementById(`val-${filterType}`); if (vDisplay) vDisplay.textContent = val; applyFabricFilters(); }); }); function applyFabricFilters() { if (!fabricImage) return; const f = fabric.Image.filters; fabricImage.filters = []; filterRanges.forEach(r => { const type = r.dataset.filter; const v = parseFloat(r.value); if (type === 'brightness' && v !== 100) fabricImage.filters.push(new f.Brightness({ brightness: (v - 100) / 100 })); if (type === 'contrast' && v !== 100) fabricImage.filters.push(new f.Contrast({ contrast: (v - 100) / 100 })); if (type === 'saturate' && v !== 100) fabricImage.filters.push(new f.Saturation({ saturation: (v - 100) / 100 })); if (type === 'blur' && v > 0) fabricImage.filters.push(new f.Blur({ blur: v / 20 })); if (type === 'sepia' && v > 0) fabricImage.filters.push(new f.Sepia()); if (type === 'grayscale' && v > 0) fabricImage.filters.push(new f.Grayscale()); if (type === 'hue-rotate' && v > 0) fabricImage.filters.push(new f.HueRotation({ rotation: v })); if (type === 'noise' && v > 0) fabricImage.filters.push(new f.Noise({ noise: v * 2 })); if (type === 'vignette' && v > 0) fabricImage.filters.push(new f.Vignette({ brightness: v / 100 })); }); fabricImage.applyFilters(); canvas.requestRenderAll(); } // --- Transform Logic --- window.rotateLeft = () => rotateCanvas(-90); window.rotateRight = () => rotateCanvas(90); window.flipH = () => { if (!fabricImage) return; fabricImage.set('flipX', !fabricImage.flipX); canvas.requestRenderAll(); }; window.flipV = () => { if (!fabricImage) return; fabricImage.set('flipY', !fabricImage.flipY); canvas.requestRenderAll(); }; function rotateCanvas(degrees) { if (!fabricImage) return; const angle = (fabricImage.angle + degrees) % 360; fabricImage.set('angle', angle); if (Math.abs(degrees) % 180 !== 0) { const w = canvas.width; const h = canvas.height; canvas.setDimensions({ width: h, height: w }); } canvas.centerObject(fabricImage); fabricImage.setCoords(); canvas.requestRenderAll(); } // --- Cropper Logic --- startCropBtn.addEventListener('click', () => { const dataUrl = canvas.toDataURL({ format: 'png' }); fabricWrapper.style.display = 'none'; cropperImg.src = dataUrl; cropperImg.style.display = 'block'; transformBar.classList.remove('d-none'); if (cropper) cropper.destroy(); cropper = new Cropper(cropperImg, { viewMode: 1, autoCropArea: 0.8, responsive: true }); }); applyCropBtn.addEventListener('click', () => { if (!cropper) return; const croppedCanvas = cropper.getCroppedCanvas(); const croppedDataUrl = croppedCanvas.toDataURL(); cropper.destroy(); cropper = null; cropperImg.style.display = 'none'; fabricWrapper.style.display = 'block'; transformBar.classList.add('d-none'); fabric.Image.fromURL(croppedDataUrl, (img) => { fabricImage = img; canvas.clear(); canvas.setWidth(img.width); canvas.setHeight(img.height); canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); }); }); // --- Decor Logic --- brushToggle.addEventListener('click', () => { isDrawing = !isDrawing; canvas.isDrawingMode = isDrawing; brushToggle.classList.toggle('active', isDrawing); canvas.freeDrawingBrush = new fabric.PencilBrush(canvas); canvas.freeDrawingBrush.color = brushColor.value; canvas.freeDrawingBrush.width = parseInt(brushSize.value, 10); }); brushColor.addEventListener('input', () => { if (canvas.freeDrawingBrush) canvas.freeDrawingBrush.color = brushColor.value; }); brushSize.addEventListener('input', () => { if (canvas.freeDrawingBrush) canvas.freeDrawingBrush.width = parseInt(brushSize.value, 10); }); addTextBtn.addEventListener('click', () => { const textStr = textInput.value.trim() || 'Nano!'; const text = new fabric.IText(textStr, { left: canvas.width / 2, top: canvas.height / 2, fontFamily: fontFamily.value, fill: textColor.value, fontSize: 50, originX: 'center', originY: 'center', cornerStyle: 'circle', cornerColor: '#FFDE59', cornerStrokeColor: '#000', transparentCorners: false }); canvas.add(text); canvas.setActiveObject(text); textInput.value = ''; }); stickerItems.forEach(item => { item.addEventListener('click', () => { const char = item.dataset.sticker; const sticker = new fabric.Text(char, { fontSize: 100, left: canvas.width / 2, top: canvas.height / 2, originX: 'center', originY: 'center', cornerStyle: 'circle' }); canvas.add(sticker); canvas.setActiveObject(sticker); }); }); // --- AI Magic --- async function performAiEdit(action, customPrompt = '') { editorLoading.classList.remove('d-none'); editorLoading.classList.add('d-flex'); const currentDataUrl = canvas.toDataURL({ format: 'png' }); try { const response = await fetch('api/edit.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: action, original_prompt: originalPrompt, edit_prompt: customPrompt, image_url: currentDataUrl }) }); const result = await response.json(); if (result.success) { fabric.Image.fromURL(result.url + '?t=' + Date.now(), (img) => { fabricImage = img; canvas.clear(); canvas.setWidth(img.getScaledWidth()); canvas.setHeight(img.getScaledHeight()); canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); editorLoading.classList.add('d-none'); }, { crossOrigin: 'anonymous' }); } else { alert('Ошибка ИИ: ' + result.error); editorLoading.classList.add('d-none'); } } catch (e) { alert('Ошибка связи с сервером'); editorLoading.classList.add('d-none'); } } applyAiMagicBtn.addEventListener('click', () => { const p = aiEditPrompt.value.trim(); if (!p) return alert('Опишите изменения'); performAiEdit('magic', p); }); removeBgBtn.addEventListener('click', () => performAiEdit('remove_bg')); upscaleBtn.addEventListener('click', () => performAiEdit('upscale')); // --- Finalize & Save --- resetEditorBtn.addEventListener('click', () => { if (confirm('Сбросить все изменения?')) { openEditor(fabricImage._originalElement.src); } }); saveEditedBtn.addEventListener('click', () => { const dataUrl = canvas.toDataURL({ format: 'png', quality: 1.0 }); const link = document.createElement('a'); link.download = `nano_edit_${Date.now()}.png`; link.href = dataUrl; link.click(); }); saveToGalleryBtn.addEventListener('click', async () => { saveToGalleryBtn.disabled = true; saveToGalleryBtn.innerHTML = ' СОХРАНЯЕМ...'; const dataUrl = canvas.toDataURL({ format: 'png', quality: 1.0 }); try { const response = await fetch('api/save.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image: dataUrl }) }); const result = await response.json(); if (result.success) { alert(result.message); location.reload(); // Refresh to see in gallery } else { alert('Ошибка: ' + result.error); } } catch (e) { alert('Ошибка сети'); } finally { saveToGalleryBtn.disabled = false; saveToGalleryBtn.innerHTML = ' СОХРАНИТЬ В ГАЛЕРЕЮ'; } }); // History interaction document.addEventListener('click', (e) => { const hBtn = e.target.closest('.history-edit-btn'); if (hBtn) { const url = hBtn.dataset.url; const prompt = hBtn.closest('.history-card').querySelector('.history-prompt').textContent; originalPrompt = prompt; openEditor(url); } }); });