snapeidit / index.html
sqibhe's picture
Add 2 files
8f20bc0 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SnapEdit Pro - Advanced Image Editor</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
background-color: #1a1a1a;
}
#editorCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: default;
transform-origin: 0 0;
}
.floating-toolbar {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(30, 30, 30, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
display: flex;
gap: 4px;
z-index: 100;
user-select: none;
transition: all 0.3s ease;
}
.floating-toolbar.dragging {
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4);
}
.tool-btn {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #e0e0e0;
background: transparent;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.tool-btn:hover {
background: rgba(99, 102, 241, 0.2);
color: white;
transform: translateY(-2px);
}
.tool-btn.active {
background: rgba(99, 102, 241, 0.4);
color: white;
box-shadow: 0 4px 8px rgba(99, 102, 241, 0.2);
}
.tool-btn i {
font-size: 16px;
}
.tool-separator {
width: 1px;
height: 24px;
background: rgba(255, 255, 255, 0.1);
margin: 0 4px;
align-self: center;
}
.floating-properties {
position: absolute;
top: 80px;
right: 20px;
background: rgba(30, 30, 30, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 16px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
width: 280px;
z-index: 100;
user-select: none;
transition: all 0.3s ease;
color: #e0e0e0;
}
.floating-properties.dragging {
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4);
}
.properties-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.properties-title {
font-size: 14px;
font-weight: 600;
color: white;
display: flex;
align-items: center;
gap: 8px;
}
.properties-close {
width: 24px;
height: 24px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #e0e0e0;
background: transparent;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.properties-close:hover {
background: rgba(255, 255, 255, 0.1);
}
.property-group {
margin-bottom: 16px;
}
.property-label {
font-size: 12px;
color: #a0a0a0;
margin-bottom: 6px;
display: block;
}
.color-picker {
width: 100%;
height: 32px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(40, 40, 40, 0.8);
cursor: pointer;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.1);
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #6366f1;
cursor: pointer;
transition: all 0.2s ease;
}
.range-value {
font-size: 12px;
color: #a0a0a0;
text-align: right;
margin-top: 4px;
}
.dropdown {
width: 100%;
padding: 8px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(40, 40, 40, 0.8);
color: #e0e0e0;
font-size: 12px;
outline: none;
}
.dropdown option {
background: #2d2d2d;
}
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.notification {
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
animation: slideIn 0.3s ease-out forwards;
background: rgba(30, 30, 30, 0.9);
color: white;
border-left: 4px solid;
}
.notification.success {
border-color: #10b981;
}
.notification.error {
border-color: #ef4444;
}
.notification.info {
border-color: #3b82f6;
}
@keyframes slideIn {
from { transform: translateX(20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.zoom-controls {
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(30, 30, 30, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
gap: 4px;
z-index: 100;
}
.zoom-btn {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #e0e0e0;
background: transparent;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.zoom-btn:hover {
background: rgba(99, 102, 241, 0.2);
color: white;
}
.zoom-value {
width: 40px;
text-align: center;
font-size: 12px;
color: white;
padding: 4px 0;
}
.fullscreen-btn {
position: fixed;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(30, 30, 30, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 100;
transition: all 0.2s ease;
}
.fullscreen-btn:hover {
background: rgba(99, 102, 241, 0.8);
transform: scale(1.1);
}
.hidden {
display: none !important;
}
.canvas-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.lock-btn {
position: absolute;
top: 5px;
right: 5px;
width: 24px;
height: 24px;
border-radius: 4px;
background: rgba(30, 30, 30, 0.8);
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
z-index: 10;
transition: all 0.2s ease;
}
.lock-btn:hover {
background: rgba(99, 102, 241, 0.8);
}
.object-controls {
position: absolute;
top: 5px;
right: 5px;
display: flex;
gap: 4px;
z-index: 10;
}
.control-btn {
width: 24px;
height: 24px;
border-radius: 4px;
background: rgba(30, 30, 30, 0.8);
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
transition: all 0.2s ease;
}
.control-btn:hover {
background: rgba(99, 102, 241, 0.8);
}
.locked {
color: #6366f1;
}
</style>
</head>
<body>
<!-- Canvas Container -->
<div class="canvas-container">
<canvas id="editorCanvas"></canvas>
</div>
<!-- Floating Toolbar -->
<div class="floating-toolbar" id="floatingToolbar">
<!-- File Actions -->
<button class="tool-btn" id="uploadBtn" title="Upload Image">
<i class="fas fa-upload"></i>
</button>
<div class="tool-separator"></div>
<!-- Selection Tools -->
<button class="tool-btn active" id="selectTool" title="Select Tool">
<i class="fas fa-mouse-pointer"></i>
</button>
<!-- Drawing Tools -->
<button class="tool-btn" id="textTool" title="Text Tool">
<i class="fas fa-font"></i>
</button>
<button class="tool-btn" id="drawTool" title="Draw Tool">
<i class="fas fa-pencil-alt"></i>
</button>
<button class="tool-btn" id="shapeTool" title="Shapes">
<i class="fas fa-square"></i>
</button>
<button class="tool-btn" id="blurTool" title="Blur Tool">
<i class="fas fa-eye-slash"></i>
</button>
<div class="tool-separator"></div>
<!-- AI Tools -->
<button class="tool-btn" id="aiRemoveBg" title="Remove Background">
<i class="fas fa-robot"></i>
</button>
<button class="tool-btn" id="aiEnhance" title="AI Enhance">
<i class="fas fa-magic"></i>
</button>
<div class="tool-separator"></div>
<!-- Edit Actions -->
<button class="tool-btn" id="undoBtn" title="Undo">
<i class="fas fa-undo"></i>
</button>
<button class="tool-btn" id="redoBtn" title="Redo">
<i class="fas fa-redo"></i>
</button>
<div class="tool-separator"></div>
<!-- Export Actions -->
<button class="tool-btn" id="saveBtn" title="Save">
<i class="fas fa-save"></i>
</button>
<button class="tool-btn" id="exportBtn" title="Export">
<i class="fas fa-file-export"></i>
</button>
</div>
<!-- Floating Properties Panel -->
<div class="floating-properties" id="floatingProperties">
<div class="properties-header">
<div class="properties-title">
<i class="fas fa-sliders-h"></i>
<span>Properties</span>
</div>
<button class="properties-close" id="propertiesClose">
<i class="fas fa-times"></i>
</button>
</div>
<div class="property-group">
<label class="property-label">Canvas Background</label>
<input type="color" class="color-picker" id="canvasBg" value="#1a1a1a">
</div>
<div class="property-group">
<label class="property-label">Opacity</label>
<input type="range" id="canvasOpacity" min="0" max="100" value="100">
<div class="range-value" id="canvasOpacityValue">100%</div>
</div>
<div class="property-group hidden" id="textProps">
<label class="property-label">Text Color</label>
<input type="color" class="color-picker" id="textColor" value="#ffffff">
<label class="property-label">Font Size</label>
<input type="range" id="textSize" min="8" max="72" value="24">
<div class="range-value" id="textSizeValue">24px</div>
<label class="property-label">Font Family</label>
<select class="dropdown" id="textFont">
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Courier New">Courier New</option>
<option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option>
</select>
</div>
<div class="property-group hidden" id="drawProps">
<label class="property-label">Brush Color</label>
<input type="color" class="color-picker" id="brushColor" value="#ffffff">
<label class="property-label">Brush Size</label>
<input type="range" id="brushSize" min="1" max="50" value="5">
<div class="range-value" id="brushSizeValue">5px</div>
</div>
<div class="property-group hidden" id="objectProps">
<label class="property-label">Object Controls</label>
<div class="flex gap-2 mt-2">
<button class="control-btn" id="lockBtn" title="Lock/Unlock">
<i class="fas fa-lock"></i>
</button>
<button class="control-btn" id="deleteBtn" title="Delete">
<i class="fas fa-trash"></i>
</button>
<button class="control-btn" id="bringToFrontBtn" title="Bring to Front">
<i class="fas fa-arrow-up"></i>
</button>
<button class="control-btn" id="sendToBackBtn" title="Send to Back">
<i class="fas fa-arrow-down"></i>
</button>
</div>
</div>
</div>
<!-- Zoom Controls -->
<div class="zoom-controls">
<button class="zoom-btn" id="zoomInBtn" title="Zoom In">
<i class="fas fa-search-plus"></i>
</button>
<div class="zoom-value" id="zoomValue">100%</div>
<button class="zoom-btn" id="zoomOutBtn" title="Zoom Out">
<i class="fas fa-search-minus"></i>
</button>
<button class="zoom-btn" id="zoomResetBtn" title="Reset Zoom">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<!-- Fullscreen Button -->
<div class="fullscreen-btn" id="fullscreenBtn" title="Toggle Fullscreen">
<i class="fas fa-expand"></i>
</div>
<!-- Notification Container -->
<div class="notification-container" id="notificationContainer"></div>
<!-- Hidden File Input -->
<input type="file" id="fileInput" accept="image/*" class="hidden">
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Fabric.js canvas
const canvas = new fabric.Canvas('editorCanvas', {
backgroundColor: '#1a1a1a',
preserveObjectStacking: true,
selection: true,
selectionColor: 'rgba(99, 102, 241, 0.3)',
selectionBorderColor: '#6366f1',
selectionLineWidth: 1,
selectionDashArray: [5, 5],
enableRetinaScaling: true
});
// Zoom state
let zoomLevel = 1;
const zoomStep = 0.1;
const minZoom = 0.1;
const maxZoom = 3;
// Set canvas to full size of window
function resizeCanvas() {
const width = window.innerWidth;
const height = window.innerHeight;
canvas.setWidth(width);
canvas.setHeight(height);
// Apply zoom transformation
canvas.setZoom(zoomLevel);
canvas.renderAll();
// Update zoom display
document.getElementById('zoomValue').textContent = `${Math.round(zoomLevel * 100)}%`;
}
// Initial resize and add event listener for window resize
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// State management
let currentTool = 'select';
let activeObject = null;
let history = [];
let historyIndex = -1;
let isDrawing = false;
let drawingPath = null;
let blurArea = null;
let polygonPoints = [];
// UI Elements
const floatingToolbar = document.getElementById('floatingToolbar');
const floatingProperties = document.getElementById('floatingProperties');
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const propertiesClose = document.getElementById('propertiesClose');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const notificationContainer = document.getElementById('notificationContainer');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const zoomResetBtn = document.getElementById('zoomResetBtn');
const lockBtn = document.getElementById('lockBtn');
const deleteBtn = document.getElementById('deleteBtn');
const bringToFrontBtn = document.getElementById('bringToFrontBtn');
const sendToBackBtn = document.getElementById('sendToBackBtn');
// Make toolbar draggable
makeDraggable(floatingToolbar);
makeDraggable(floatingProperties);
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// Only drag if clicking on the header or empty space
if (e.target.classList.contains('tool-btn') ||
e.target.classList.contains('properties-close') ||
e.target.closest('.tool-btn') ||
e.target.closest('.properties-close')) {
return;
}
element.classList.add('dragging');
// Get the mouse cursor position at startup
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// Calculate the new cursor position
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// Set the element's new position
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// Stop moving when mouse button is released
document.onmouseup = null;
document.onmousemove = null;
element.classList.remove('dragging');
}
}
// Zoom functionality
zoomInBtn.addEventListener('click', zoomIn);
zoomOutBtn.addEventListener('click', zoomOut);
zoomResetBtn.addEventListener('click', resetZoom);
function zoomIn() {
if (zoomLevel < maxZoom) {
zoomLevel = Math.min(zoomLevel + zoomStep, maxZoom);
applyZoom();
}
}
function zoomOut() {
if (zoomLevel > minZoom) {
zoomLevel = Math.max(zoomLevel - zoomStep, minZoom);
applyZoom();
}
}
function resetZoom() {
zoomLevel = 1;
applyZoom();
}
function applyZoom() {
canvas.setZoom(zoomLevel);
document.getElementById('zoomValue').textContent = `${Math.round(zoomLevel * 100)}%`;
canvas.renderAll();
}
// Fullscreen functionality
fullscreenBtn.addEventListener('click', toggleFullscreen);
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().then(() => {
fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>';
});
} else {
document.exitFullscreen().then(() => {
fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>';
});
}
}
// File upload handler
uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileUpload);
function handleFileUpload(e) {
const file = e.target.files[0];
if (!file) return;
// Validate file type
if (!file.type.match('image.*')) {
showNotification('Please select an image file (JPG, PNG, GIF)', 'error');
return;
}
// Validate file size
if (file.size > 10 * 1024 * 1024) {
showNotification('Image size should be less than 10MB', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(f) {
fabric.Image.fromURL(f.target.result, function(img) {
// Clear canvas first
canvas.clear();
// Add the image to canvas
canvas.add(img);
canvas.setActiveObject(img);
canvas.renderAll();
// Center the image
img.center();
// Add lock controls to the image
addLockControls(img);
showNotification('Image loaded successfully!', 'success');
// Save initial state
saveHistory();
}, {
crossOrigin: 'anonymous',
// Enable selection and editing of the image
selectable: true,
hasControls: true,
hasBorders: true
});
};
reader.onerror = function() {
showNotification('Error reading file. Please try another image.', 'error');
};
reader.readAsDataURL(file);
}
// Add lock controls to an object
function addLockControls(obj) {
// Create a lock button
const lockButton = document.createElement('div');
lockButton.className = 'lock-btn';
lockButton.innerHTML = '<i class="fas fa-lock-open"></i>';
lockButton.title = 'Lock/Unlock';
// Add event listener for lock button
lockButton.addEventListener('click', function(e) {
e.stopPropagation();
toggleLock(obj);
});
// Position the lock button relative to the object
obj.lockButton = lockButton;
updateLockButtonPosition(obj);
// Add the lock button to the canvas container
document.querySelector('.canvas-container').appendChild(lockButton);
// Update position when object moves
obj.on('moving', function() {
if (!obj.locked) {
updateLockButtonPosition(obj);
}
});
// Update position when object scales
obj.on('scaling', function() {
if (!obj.locked) {
updateLockButtonPosition(obj);
}
});
// Remove button when object is removed
obj.on('removed', function() {
if (obj.lockButton && obj.lockButton.parentNode) {
obj.lockButton.parentNode.removeChild(obj.lockButton);
}
});
}
// Update lock button position based on object position
function updateLockButtonPosition(obj) {
if (!obj.lockButton) return;
const zoom = canvas.getZoom();
const vpt = canvas.viewportTransform;
const absoluteLeft = obj.left * zoom + vpt[4];
const absoluteTop = obj.top * zoom + vpt[5];
obj.lockButton.style.left = `${absoluteLeft + obj.width * zoom - 30}px`;
obj.lockButton.style.top = `${absoluteTop + 5}px`;
}
// Toggle lock state of an object
function toggleLock(obj) {
obj.locked = !obj.locked;
if (obj.locked) {
obj.lockButton.innerHTML = '<i class="fas fa-lock locked"></i>';
obj.selectable = false;
obj.hasControls = false;
obj.hasBorders = false;
obj.evented = false;
showNotification('Object locked', 'success');
} else {
obj.lockButton.innerHTML = '<i class="fas fa-lock-open"></i>';
obj.selectable = true;
obj.hasControls = true;
obj.hasBorders = true;
obj.evented = true;
showNotification('Object unlocked', 'success');
}
canvas.renderAll();
saveHistory();
}
// Properties panel close button
propertiesClose.addEventListener('click', () => {
floatingProperties.classList.add('hidden');
});
// Canvas background color picker
document.getElementById('canvasBg').addEventListener('input', function(e) {
canvas.setBackgroundColor(e.target.value, canvas.renderAll.bind(canvas));
saveHistory();
});
// Canvas opacity slider
document.getElementById('canvasOpacity').addEventListener('input', function(e) {
const opacity = e.target.value / 100;
canvas.setBackgroundColor(canvas.backgroundColor, canvas.renderAll.bind(canvas), {
opacity: opacity
});
document.getElementById('canvasOpacityValue').textContent = `${e.target.value}%`;
saveHistory();
});
// Object control buttons
lockBtn.addEventListener('click', function() {
if (activeObject) {
toggleLock(activeObject);
}
});
deleteBtn.addEventListener('click', function() {
if (activeObject) {
deleteSelectedObject();
}
});
bringToFrontBtn.addEventListener('click', function() {
if (activeObject) {
activeObject.bringToFront();
canvas.renderAll();
saveHistory();
}
});
sendToBackBtn.addEventListener('click', function() {
if (activeObject) {
activeObject.sendToBack();
canvas.renderAll();
saveHistory();
}
});
// Helper function to delete selected object
function deleteSelectedObject() {
if (activeObject) {
canvas.remove(activeObject);
activeObject = null;
canvas.renderAll();
saveHistory();
showNotification('Object deleted', 'success');
}
}
// Helper function to show notifications
function showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
let icon;
if (type === 'success') {
icon = '<i class="fas fa-check-circle"></i>';
} else if (type === 'error') {
icon = '<i class="fas fa-exclamation-circle"></i>';
} else {
icon = '<i class="fas fa-info-circle"></i>';
}
notification.innerHTML = `${icon} ${message}`;
notificationContainer.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
// History management
function saveHistory() {
const canvasState = JSON.stringify(canvas.toJSON());
if (historyIndex < history.length - 1) {
history = history.slice(0, historyIndex + 1);
}
history.push(canvasState);
historyIndex++;
if (history.length > 50) {
history.shift();
historyIndex--;
}
}
function loadHistory(index) {
if (index >= 0 && index < history.length) {
canvas.loadFromJSON(history[index], function() {
canvas.renderAll();
});
}
}
// Undo/Redo functionality
document.getElementById('undoBtn').addEventListener('click', function() {
if (historyIndex > 0) {
historyIndex--;
loadHistory(historyIndex);
}
});
document.getElementById('redoBtn').addEventListener('click', function() {
if (historyIndex < history.length - 1) {
historyIndex++;
loadHistory(historyIndex);
}
});
// Tool selection
function setActiveTool(tool) {
currentTool = tool;
// Update active button
document.querySelectorAll('.tool-btn').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(`${tool}Tool`).classList.add('active');
// Update canvas cursor
switch(tool) {
case 'select':
canvas.defaultCursor = 'default';
canvas.selection = true;
break;
case 'text':
canvas.defaultCursor = 'text';
canvas.selection = false;
break;
case 'draw':
canvas.defaultCursor = 'crosshair';
canvas.selection = false;
break;
case 'shape':
canvas.defaultCursor = 'crosshair';
canvas.selection = false;
break;
case 'blur':
canvas.defaultCursor = 'crosshair';
canvas.selection = false;
break;
}
// Show relevant properties
document.querySelectorAll('.property-group').forEach(group => {
group.classList.add('hidden');
});
if (tool === 'text') {
document.getElementById('textProps').classList.remove('hidden');
} else if (tool === 'draw') {
document.getElementById('drawProps').classList.remove('hidden');
}
// Show properties panel if it's hidden
if (tool !== 'select') {
floatingProperties.classList.remove('hidden');
}
}
// Set up tool buttons
document.getElementById('selectTool').addEventListener('click', () => setActiveTool('select'));
document.getElementById('textTool').addEventListener('click', () => setActiveTool('text'));
document.getElementById('drawTool').addEventListener('click', () => setActiveTool('draw'));
document.getElementById('shapeTool').addEventListener('click', () => setActiveTool('shape'));
document.getElementById('blurTool').addEventListener('click', () => setActiveTool('blur'));
// Initialize with select tool
setActiveTool('select');
// Canvas event listeners for tools
canvas.on('mouse:down', function(options) {
if (options.target && options.target.locked) {
return;
}
if (options.target) {
activeObject = options.target;
return;
}
const pointer = canvas.getPointer(options.e);
switch(currentTool) {
case 'text':
addText(pointer);
break;
case 'draw':
startDrawing(pointer);
break;
case 'shape':
addRectangle(pointer);
break;
case 'blur':
addBlur(pointer);
break;
}
});
canvas.on('mouse:move', function(options) {
if (isDrawing && drawingPath && currentTool === 'draw') {
const pointer = canvas.getPointer(options.e);
drawingPath.path.push([
'L',
pointer.x,
pointer.y
]);
canvas.renderAll();
}
});
canvas.on('mouse:up', function() {
if (isDrawing) {
isDrawing = false;
saveHistory();
}
});
canvas.on('object:selected', function(options) {
if (options.target.locked) {
canvas.discardActiveObject();
return;
}
activeObject = options.target;
floatingProperties.classList.remove('hidden');
// Show object controls in properties panel
document.getElementById('objectProps').classList.remove('hidden');
// Update lock button state
if (activeObject.locked) {
lockBtn.innerHTML = '<i class="fas fa-lock locked"></i>';
} else {
lockBtn.innerHTML = '<i class="fas fa-lock-open"></i>';
}
});
canvas.on('selection:cleared', function() {
activeObject = null;
document.getElementById('objectProps').classList.add('hidden');
});
// Tool functions
function addText(position) {
const text = new fabric.IText('Type here', {
left: position.x,
top: position.y,
fontFamily: document.getElementById('textFont').value,
fontSize: parseInt(document.getElementById('textSize').value),
fill: document.getElementById('textColor').value,
selectable: true,
hasControls: true,
hasBorders: true
});
canvas.add(text);
canvas.setActiveObject(text);
text.enterEditing();
text.hiddenTextarea.focus();
canvas.renderAll();
saveHistory();
}
function startDrawing(position) {
isDrawing = true;
drawingPath = new fabric.Path(`M ${position.x} ${position.y}`, {
stroke: document.getElementById('brushColor').value,
strokeWidth: parseInt(document.getElementById('brushSize').value),
fill: 'transparent',
selectable: true,
hasControls: true,
hasBorders: true
});
canvas.add(drawingPath);
}
function addRectangle(position) {
const rect = new fabric.Rect({
left: position.x,
top: position.y,
width: 100,
height: 100,
fill: 'transparent',
stroke: '#ffffff',
strokeWidth: 2,
selectable: true,
hasControls: true,
hasBorders: true
});
canvas.add(rect);
canvas.setActiveObject(rect);
canvas.renderAll();
saveHistory();
// Add lock controls to the rectangle
addLockControls(rect);
}
function addBlur(position) {
const circle = new fabric.Circle({
left: position.x,
top: position.y,
radius: 30,
fill: 'rgba(0,0,0,0.5)',
selectable: true,
hasControls: true,
hasBorders: true
});
canvas.add(circle);
canvas.setActiveObject(circle);
canvas.renderAll();
saveHistory();
// Add lock controls to the blur circle
addLockControls(circle);
}
// Text properties
document.getElementById('textColor').addEventListener('input', function(e) {
if (activeObject && activeObject.type === 'i-text') {
activeObject.set('fill', e.target.value);
canvas.renderAll();
saveHistory();
}
});
document.getElementById('textSize').addEventListener('input', function(e) {
if (activeObject && activeObject.type === 'i-text') {
activeObject.set('fontSize', parseInt(e.target.value));
document.getElementById('textSizeValue').textContent = `${e.target.value}px`;
canvas.renderAll();
saveHistory();
}
});
document.getElementById('textFont').addEventListener('change', function(e) {
if (activeObject && activeObject.type === 'i-text') {
activeObject.set('fontFamily', e.target.value);
canvas.renderAll();
saveHistory();
}
});
// Brush properties
document.getElementById('brushColor').addEventListener('input', function(e) {
// This would affect new drawings
});
document.getElementById('brushSize').addEventListener('input', function(e) {
document.getElementById('brushSizeValue').textContent = `${e.target.value}px`;
});
// Save/Export functionality (simplified)
document.getElementById('saveBtn').addEventListener('click', function() {
const dataURL = canvas.toDataURL({
format: 'png',
quality: 1
});
const link = document.createElement('a');
link.download = 'snapedit-export.png';
link.href = dataURL;
link.click();
showNotification('Image exported successfully!', 'success');
});
document.getElementById('exportBtn').addEventListener('click', function() {
// In a real app, this would show export options
showNotification('Export options would appear here', 'info');
});
// AI Tools (simplified)
document.getElementById('aiRemoveBg').addEventListener('click', function() {
showNotification('AI Background Removal would run here', 'info');
});
document.getElementById('aiEnhance').addEventListener('click', function() {
showNotification('AI Enhancement would run here', 'info');
});
// Add keyboard shortcuts for zoom and delete
document.addEventListener('keydown', function(e) {
// Check for Ctrl/Cmd key
const ctrlKey = e.ctrlKey || e.metaKey;
// Zoom shortcuts
if (ctrlKey && e.key === '+') {
e.preventDefault();
zoomIn();
} else if (ctrlKey && e.key === '-') {
e.preventDefault();
zoomOut();
} else if (ctrlKey && e.key === '0') {
e.preventDefault();
resetZoom();
}
// Delete/Backspace shortcut
if ((e.key === 'Delete' || e.key === 'Backspace') && activeObject) {
e.preventDefault();
deleteSelectedObject();
}
});
// Update lock button positions when zooming
canvas.on('after:render', function() {
canvas.forEachObject(function(obj) {
if (obj.lockButton) {
updateLockButtonPosition(obj);
}
});
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=sqibhe/snapeidit" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>