anycoder-29639380 / index.html
Bbkblo's picture
Upload folder using huggingface_hub
b99f9fa verified
Raw
History Blame Contribute Delete
39.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CivilCAD - Engineering Drawing Application</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #1e1e1e;
--bg-secondary: #252526;
--bg-tertiary: #2d2d30;
--bg-hover: #3e3e42;
--text-primary: #cccccc;
--text-secondary: #969696;
--accent: #007acc;
--accent-hover: #1e90ff;
--border: #3e3e42;
--success: #4ec9b0;
--warning: #ce9178;
--error: #f44747;
--grid-color: #2a2a2a;
--snap-color: #4a9eff;
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
overflow: hidden;
user-select: none;
}
.app-container {
display: flex;
height: 100vh;
flex-direction: column;
}
/* Header */
.header {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
padding: 8px 16px;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 48px;
}
.header-left {
display: flex;
align-items: center;
gap: 20px;
}
.logo {
font-size: 20px;
font-weight: 600;
color: var(--accent);
display: flex;
align-items: center;
gap: 8px;
}
.logo::before {
content: "⚙";
font-size: 24px;
}
.header-nav {
display: flex;
gap: 4px;
}
.nav-btn {
padding: 6px 12px;
background: transparent;
color: var(--text-primary);
border: none;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
font-size: 13px;
}
.nav-btn:hover {
background: var(--bg-hover);
}
/* Main Layout */
.main-content {
display: flex;
flex: 1;
overflow: hidden;
}
/* Toolbar */
.toolbar {
background: var(--bg-secondary);
width: 60px;
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 0;
gap: 4px;
border-right: 1px solid var(--border);
}
.tool-btn {
width: 44px;
height: 44px;
background: transparent;
color: var(--text-secondary);
border: none;
cursor: pointer;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition: all 0.2s;
position: relative;
}
.tool-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.tool-btn.active {
background: var(--accent);
color: white;
}
.tool-tooltip {
position: absolute;
left: 54px;
background: var(--bg-tertiary);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
z-index: 1000;
border: 1px solid var(--border);
}
.tool-btn:hover .tool-tooltip {
opacity: 1;
}
.tool-separator {
width: 30px;
height: 1px;
background: var(--border);
margin: 4px 0;
}
/* Canvas Area */
.canvas-container {
flex: 1;
background: var(--bg-primary);
position: relative;
overflow: hidden;
}
#drawingCanvas {
position: absolute;
top: 0;
left: 0;
cursor: crosshair;
}
.canvas-grid {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
/* Properties Panel */
.properties-panel {
background: var(--bg-secondary);
width: 280px;
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
}
.panel-header {
padding: 12px 16px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border);
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-content {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.property-group {
margin-bottom: 20px;
}
.property-label {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.property-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.property-input {
flex: 1;
background: var(--bg-tertiary);
border: 1px solid var(--border);
color: var(--text-primary);
padding: 6px 10px;
border-radius: 4px;
font-size: 13px;
}
.property-input:focus {
outline: none;
border-color: var(--accent);
}
.property-select {
flex: 1;
background: var(--bg-tertiary);
border: 1px solid var(--border);
color: var(--text-primary);
padding: 6px 10px;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
}
/* Layers Panel */
.layers-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.layer-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
background: var(--bg-tertiary);
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
.layer-item:hover {
background: var(--bg-hover);
}
.layer-item.active {
background: var(--accent);
color: white;
}
.layer-visibility {
width: 16px;
height: 16px;
cursor: pointer;
}
.layer-name {
flex: 1;
font-size: 13px;
}
.layer-color {
width: 16px;
height: 16px;
border-radius: 2px;
border: 1px solid var(--border);
}
/* Status Bar */
.status-bar {
background: var(--bg-tertiary);
border-top: 1px solid var(--border);
padding: 4px 16px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: var(--text-secondary);
min-height: 28px;
}
.status-left {
display: flex;
gap: 20px;
}
.status-right {
display: flex;
gap: 20px;
}
.status-item {
display: flex;
align-items: center;
gap: 4px;
}
.status-label {
color: var(--text-secondary);
}
.status-value {
color: var(--text-primary);
font-weight: 500;
}
/* Context Menu */
.context-menu {
position: absolute;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 4px;
padding: 4px 0;
display: none;
z-index: 1000;
min-width: 150px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.context-menu-item {
padding: 6px 12px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.context-menu-item:hover {
background: var(--bg-hover);
}
/* Color Picker */
.color-picker {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.color-option {
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s;
}
.color-option:hover {
transform: scale(1.1);
}
.color-option.selected {
border-color: white;
box-shadow: 0 0 8px rgba(255,255,255,0.3);
}
/* Snap Indicator */
.snap-indicator {
position: absolute;
width: 8px;
height: 8px;
background: var(--snap-color);
border-radius: 50%;
pointer-events: none;
display: none;
z-index: 100;
transform: translate(-50%, -50%);
}
/* Dimension Text */
.dimension-text {
position: absolute;
background: var(--bg-primary);
padding: 2px 6px;
border-radius: 2px;
font-size: 11px;
color: var(--text-primary);
pointer-events: none;
white-space: nowrap;
border: 1px solid var(--border);
}
/* Buttons */
.btn {
padding: 6px 12px;
background: var(--accent);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
}
.btn:hover {
background: var(--accent-hover);
}
.btn-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.btn-secondary:hover {
background: var(--bg-hover);
}
.btn-small {
padding: 4px 8px;
font-size: 12px;
}
/* Zoom Controls */
.zoom-controls {
position: absolute;
bottom: 20px;
right: 300px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 6px;
padding: 4px;
display: flex;
gap: 4px;
align-items: center;
}
.zoom-btn {
width: 28px;
height: 28px;
background: transparent;
color: var(--text-primary);
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.zoom-btn:hover {
background: var(--bg-hover);
}
.zoom-level {
padding: 0 8px;
font-size: 12px;
color: var(--text-secondary);
min-width: 50px;
text-align: center;
}
/* Loading Overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(30, 30, 30, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.loading-spinner {
width: 48px;
height: 48px;
border: 4px solid var(--bg-tertiary);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Responsive */
@media (max-width: 768px) {
.properties-panel {
position: absolute;
right: -280px;
transition: right 0.3s;
z-index: 100;
}
.properties-panel.open {
right: 0;
}
.zoom-controls {
right: 20px;
}
}
/* Link styling */
.credit-link {
color: var(--accent);
text-decoration: none;
font-size: 12px;
}
.credit-link:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="app-container">
<!-- Header -->
<header class="header">
<div class="header-left">
<div class="logo">CivilCAD</div>
<nav class="header-nav">
<button class="nav-btn" onclick="newDrawing()">New</button>
<button class="nav-btn" onclick="saveDrawing()">Save</button>
<button class="nav-btn" onclick="loadDrawing()">Load</button>
<button class="nav-btn" onclick="exportImage()">Export</button>
</nav>
</div>
<div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" class="credit-link" target="_blank">Built with anycoder</a>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<!-- Toolbar -->
<aside class="toolbar">
<button class="tool-btn active" data-tool="pointer" onclick="selectTool('pointer')">
<span class="tool-tooltip">Select (S)</span>
</button>
<button class="tool-btn" data-tool="line" onclick="selectTool('line')">
<span class="tool-tooltip">Line (L)</span>
</button>
<button class="tool-btn" data-tool="rectangle" onclick="selectTool('rectangle')">
<span class="tool-tooltip">Rectangle (R)</span>
</button>
<button class="tool-btn" data-tool="circle" onclick="selectTool('circle')">
<span class="tool-tooltip">Circle (C)</span>
</button>
<button class="tool-btn" data-tool="arc" onclick="selectTool('arc')">
<span class="tool-tooltip">Arc (A)</span>
</button>
<button class="tool-btn" data-tool="polyline" onclick="selectTool('polyline')">
<span class="tool-tooltip">Polyline (P)</span>
</button>
<button class="tool-btn" data-tool="dimension" onclick="selectTool('dimension')">
📏
<span class="tool-tooltip">Dimension (D)</span>
</button>
<button class="tool-btn" data-tool="text" onclick="selectTool('text')">
T
<span class="tool-tooltip">Text (T)</span>
</button>
<div class="tool-separator"></div>
<button class="tool-btn" data-tool="pan" onclick="selectTool('pan')">
<span class="tool-tooltip">Pan (H)</span>
</button>
<button class="tool-btn" data-tool="zoom" onclick="selectTool('zoom')">
🔍
<span class="tool-tooltip">Zoom Window (Z)</span>
</button>
<div class="tool-separator"></div>
<button class="tool-btn" onclick="undo()">
<span class="tool-tooltip">Undo (Ctrl+Z)</span>
</button>
<button class="tool-btn" onclick="redo()">
<span class="tool-tooltip">Redo (Ctrl+Y)</span>
</button>
<button class="tool-btn" onclick="deleteSelected()">
🗑
<span class="tool-tooltip">Delete (Del)</span>
</button>
</aside>
<!-- Canvas Container -->
<main class="canvas-container" id="canvasContainer">
<canvas id="drawingCanvas"></canvas>
<canvas class="canvas-grid" id="gridCanvas"></canvas>
<div class="snap-indicator" id="snapIndicator"></div>
<!-- Zoom Controls -->
<div class="zoom-controls">
<button class="zoom-btn" onclick="zoomOut()"></button>
<span class="zoom-level" id="zoomLevel">100%</span>
<button class="zoom-btn" onclick="zoomIn()">+</button>
<button class="zoom-btn" onclick="fitToScreen()"></button>
</div>
</main>
<!-- Properties Panel -->
<aside class="properties-panel">
<div class="panel-header">
Properties
<button class="btn btn-small btn-secondary" onclick="togglePanel()">Hide</button>
</div>
<div class="panel-content">
<!-- Drawing Properties -->
<div class="property-group">
<div class="property-label">Drawing</div>
<div class="property-row">
<label style="font-size: 12px;">Units:</label>
<select class="property-select" id="units" onchange="updateUnits()">
<option value="mm">Millimeters</option>
<option value="cm">Centimeters</option>
<option value="m" selected>Meters</option>
<option value="ft">Feet</option>
<option value="in">Inches</option>
</select>
</div>
<div class="property-row">
<label style="font-size: 12px;">Grid:</label>
<input type="checkbox" id="gridToggle" checked onchange="toggleGrid()">
<label style="font-size: 12px;">Snap:</label>
<input type="checkbox" id="snapToggle" checked onchange="toggleSnap()">
</div>
<div class="property-row">
<label style="font-size: 12px;">Grid Size:</label>
<input type="number" class="property-input" id="gridSize" value="10" min="1" max="100" onchange="updateGridSize()">
</div>
</div>
<!-- Line Properties -->
<div class="property-group">
<div class="property-label">Line</div>
<div class="property-row">
<label style="font-size: 12px;">Color:</label>
<div class="color-picker" id="lineColorPicker">
<div class="color-option selected" style="background: #ffffff" data-color="#ffffff"></div>
<div class="color-option" style="background: #ff0000" data-color="#ff0000"></div>
<div class="color-option" style="background: #00ff00" data-color="#00ff00"></div>
<div class="color-option" style="background: #0000ff" data-color="#0000ff"></div>
<div class="color-option" style="background: #ffff00" data-color="#ffff00"></div>
<div class="color-option" style="background: #ff00ff" data-color="#ff00ff"></div>
<div class="color-option" style="background: #00ffff" data-color="#00ffff"></div>
<div class="color-option" style="background: #ff8800" data-color="#ff8800"></div>
</div>
</div>
<div class="property-row">
<label style="font-size: 12px;">Width:</label>
<input type="number" class="property-input" id="lineWidth" value="1" min="0.1" max="10" step="0.1">
</div>
<div class="property-row">
<label style="font-size: 12px;">Style:</label>
<select class="property-select" id="lineStyle">
<option value="solid">Solid</option>
<option value="dashed">Dashed</option>
<option value="dotted">Dotted</option>
<option value="center">Center Line</option>
</select>
</div>
</div>
<!-- Layers -->
<div class="property-group">
<div class="property-label">Layers</div>
<div class="layers-list" id="layersList">
<div class="layer-item active" data-layer="0">
<input type="checkbox" class="layer-visibility" checked>
<span class="layer-name">Layer 1</span>
<div class="layer-color" style="background: #ffffff"></div>
</div>
</div>
<div style="margin-top: 8px; display: flex; gap: 4px;">
<button class="btn btn-small" onclick="addLayer()">Add</button>
<button class="btn btn-small btn-secondary" onclick="deleteLayer()">Delete</button>
</div>
</div>
<!-- Object Info -->
<div class="property-group">
<div class="property-label">Selected Object</div>
<div id="objectInfo">
<div style="font-size: 12px; color: var(--text-secondary);">No object selected</div>
</div>
</div>
</div>
</aside>
</div>
<!-- Status Bar -->
<footer class="status-bar">
<div class="status-left">
<div class="status-item">
<span class="status-label">X:</span>
<span class="status-value" id="mouseX">0.00</span>
</div>
<div class="status-item">
<span class="status-label">Y:</span>
<span class="status-value" id="mouseY">0.00</span>
</div>
<div class="status-item">
<span class="status-label">Tool:</span>
<span class="status-value" id="currentTool">Select</span>
</div>
</div>
<div class="status-right">
<div class="status-item">
<span class="status-label">Objects:</span>
<span class="status-value" id="objectCount">0</span>
</div>
<div class="status-item">
<span class="status-label">Layer:</span>
<span class="status-value" id="currentLayer">Layer 1</span>
</div>
<div class="status-item">
<span class="status-label">Snap:</span>
<span class="status-value" id="snapStatus">ON</span>
</div>
</div>
</footer>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="copyObject()">Copy</div>
<div class="context-menu-item" onclick="pasteObject()">Paste</div>
<div class="context-menu-item" onclick="deleteSelected()">Delete</div>
<div class="context-menu-item" onclick="bringToFront()">Bring to Front</div>
<div class="context-menu-item" onclick="sendToBack()">Send to Back</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner"></div>
</div>
<script>
// Global Variables
const canvas = document.getElementById('drawingCanvas');
const ctx = canvas.getContext('2d');
const gridCanvas = document.getElementById('gridCanvas');
const gridCtx = gridCanvas.getContext('2d');
let currentTool = 'pointer';
let isDrawing = false;
let startX, startY;
let objects = [];
let selectedObject = null;
let currentLayer = 0;
let layers = [{ name: 'Layer 1', visible: true, color: '#ffffff' }];
let zoom = 1;
let panX = 0, panY = 0;
let isPanning = false;
let lastPanX, lastPanY;
let gridSize = 10;
let snapEnabled = true;
let gridEnabled = true;
let units = 'm';
let history = [];
let historyStep = -1;
let clipboard = null;
let tempPoints = [];
let lineColor = '#ffffff';
let lineWidth = 1;
let lineStyle = 'solid';
// Initialize
function init() {
resizeCanvas();
drawGrid();
setupEventListeners();
setupColorPicker();
window.addEventListener('resize', resizeCanvas);
}
// Resize Canvas
function resizeCanvas() {
const container = document.getElementById('canvasContainer');
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
gridCanvas.width = container.clientWidth;
gridCanvas.height = container.clientHeight;
redraw();
drawGrid();
}
// Setup Event Listeners
function setupEventListeners() {
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('wheel', handleWheel);
canvas.addEventListener('contextmenu', handleContextMenu);
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
}
// Mouse Events
function handleMouseDown(e) {
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left - panX) / zoom;
const y = (e.clientY - rect.top - panY) / zoom;
if (snapEnabled) {
const snapped = snapToGrid(x, y);
startX = snapped.x;
startY = snapped.y;
} else {
startX = x;
startY = y;
}
if (currentTool === 'pointer') {
selectObjectAt(x, y);
} else if (currentTool === 'pan') {
isPanning = true;
lastPanX = e.clientX;
lastPanY = e.clientY;
canvas.style.cursor = 'grabbing';
} else if (currentTool === 'polyline') {
tempPoints.push({ x: startX, y: startY });
if (tempPoints.length > 1) {
drawPolylinePreview();
}
} else if (currentTool === 'text') {
const text = prompt('Enter text:');
if (text) {
addObject({
type: 'text',
x: startX,
y: startY,
text: text,
color: lineColor,
layer: currentLayer
});
}
} else {
isDrawing = true;
}
}
function handleMouseMove(e) {
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left - panX) / zoom;
const y = (e.clientY - rect.top - panY) / zoom;
updateMousePosition(x, y);
if (snapEnabled && currentTool !== 'pan') {
const snapped = snapToGrid(x, y);
showSnapIndicator(snapped.x, snapped.y);
} else {
hideSnapIndicator();
}
if (isPanning) {
panX += e.clientX - lastPanX;
panY += e.clientY - lastPanY;
lastPanX = e.clientX;
lastPanY = e.clientY;
redraw();
drawGrid();
} else if (isDrawing) {
redraw();
drawPreview(x, y);
} else if (currentTool === 'polyline' && tempPoints.length > 0) {
redraw();
drawPolylinePreview(x, y);
}
}
function handleMouseUp(e) {
if (isPanning) {
isPanning = false;
canvas.style.cursor = 'grab';
return;
}
if (!isDrawing) return;
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left - panX) / zoom;
const y = (e.clientY - rect.top - panY) / zoom;
let endX = x, endY = y;
if (snapEnabled) {
const snapped = snapToGrid(x, y);
endX = snapped.x;
endY = snapped.y;
}
if (currentTool === 'line') {
addObject({
type: 'line',
x1: startX,
y1: startY,
x2: endX,
y2: endY,
color: lineColor,
width: lineWidth,
style: lineStyle,
layer: currentLayer
});
} else if (currentTool === 'rectangle') {
addObject({
type: 'rectangle',
x: Math.min(startX, endX),
y: Math.min(startY, endY),
width: Math.abs(endX - startX),
height: Math.abs(endY - startY),
color: lineColor,
lineWidth: lineWidth,
style: lineStyle,
layer: currentLayer
});
} else if (currentTool === 'circle') {
const radius = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
addObject({
type: 'circle',
x: startX,
y: startY,
radius: radius,
color: lineColor,
lineWidth: lineWidth,
style: lineStyle,
layer: currentLayer
});
} else if (currentTool === 'arc') {
const radius = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
const angle = Math.atan2(endY - startY, endX - startX);
addObject({
type: 'arc',
x: startX,
y: startY,
radius: radius,
startAngle: 0,
endAngle: angle,
color: lineColor,
lineWidth: lineWidth,
style: lineStyle,
layer: currentLayer
});
} else if (currentTool === 'dimension') {
const distance = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
const midX = (startX + endX) / 2;
const midY = (startY + endY) / 2;
addObject({
type: 'dimension',
x1: startX,
y1: startY,
x2: endX,
y2: endY,
text: formatDistance(distance),
color: lineColor,
layer: currentLayer
});
} else if (currentTool === 'zoom') {
const zoomRect = {
x: Math.min(startX, endX),
y: Math.min(startY, endY),
width: Math.abs(endX - startX),
height: Math.abs(endY - startY)
};
zoomToRect(zoomRect);
}
isDrawing = false;
redraw();
}
function handleWheel(e) {
e.preventDefault();
const delta = e.deltaY > 0 ? 0.9 : 1.1;
const newZoom = Math.max(0.1, Math.min(10, zoom * delta));
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
panX = mouseX - (mouseX - panX) * (newZoom / zoom);
panY = mouseY - (mouseY - panY) * (newZoom / zoom);
zoom = newZoom;
updateZoomDisplay();
redraw();
drawGrid();
}
function handleContextMenu(e) {
e.preventDefault();
const contextMenu = document.getElementById('contextMenu');
contextMenu.style.display = 'block';
contextMenu.style.left = e.clientX + 'px';
contextMenu.style.top = e.clientY + 'px';
}
function handleKeyDown(e) {
// Keyboard shortcuts
if (e.ctrlKey || e.metaKey) {
switch(e.key.toLowerCase()) {
case 'z':
e.preventDefault();
undo();
break;
case 'y':
e.preventDefault();
redo();
break;
case 's':
e.preventDefault();
saveDrawing();
break;
case 'o':
e.preventDefault();
loadDrawing();
break;
case 'c':
e.preventDefault();
copyObject();
break;
case 'v':
e.preventDefault();
pasteObject();
break;
}
} else {
switch(e.key.toLowerCase()) {
case 'delete':
case 'backspace':
deleteSelected();
break;
case 's':
selectTool('pointer');
break;
case 'l':
selectTool('line');
break;
case 'r':
selectTool('rectangle');
break;
case 'c':
selectTool('circle');
break;
case 'a':
selectTool('arc');
break;
case 'p':
selectTool('polyline');
break;
case 'd':
selectTool('dimension');
break;
case 't':
selectTool('text');
break;
case 'h':
selectTool('pan');
break;
case 'z':
selectTool('zoom');
break;
case 'escape':
if (currentTool === 'polyline') {
if (tempPoints.length > 1) {
addObject({
type: 'polyline',
points: [...tempPoints],
color: lineColor,
width: lineWidth,
style: lineStyle,
layer: currentLayer
});
}
tempPoints = [];
redraw();
}
break;
}
}
}
function handleKeyUp(e) {
document.addEventListener('click', () => {
document.getElementById('contextMenu').style.display = 'none';
}, { once: true });
}
// Tool Selection
function selectTool(tool) {
currentTool = tool;
document.querySelectorAll('.tool-btn').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-tool="${tool}"]`).classList.add('active');
document.getElementById('currentTool').textContent =
tool.charAt(0).toUpperCase() + tool.slice(1);
// Update cursor
if (tool === 'pan') {
canvas.style.cursor = 'grab';
} else if (tool === 'zoom') {
canvas.style.cursor = 'zoom-in';
} else if (tool === 'text') {
canvas.style.cursor = 'text';
} else {
canvas.style.cursor = 'crosshair';
}
// Clear polyline if switching tools
if (tool !== 'polyline' && tempPoints.length > 0) {
if (tempPoints.length > 1) {
addObject({
type: 'polyline',
points: [...tempPoints],
color: lineColor,
width: lineWidth,
style: lineStyle,
layer: currentLayer
});
}
tempPoints = [];
redraw();
}
}
// Drawing Functions
function drawGrid() {