37689-vm/assets/js/main.js
2026-01-26 01:57:49 +00:00

475 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = '<span class="spinner-border spinner-border-sm"></span> СОХРАНЯЕМ...';
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 = '<i class="fas fa-cloud-upload-alt me-2"></i> СОХРАНИТЬ В ГАЛЕРЕЮ';
}
});
// 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);
}
});
});