From d5787d3972b5031fab487971fdbc460ac3255193 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Fri, 12 Dec 2025 03:37:48 +0500 Subject: [PATCH] clean up for fresh build, from scratch with more features --- README.md | 207 ------ app.js | 2003 ---------------------------------------------------- index.html | 78 -- styles.css | 244 ------- 4 files changed, 2532 deletions(-) delete mode 100644 README.md delete mode 100644 app.js delete mode 100644 index.html delete mode 100644 styles.css diff --git a/README.md b/README.md deleted file mode 100644 index 69c9750..0000000 --- a/README.md +++ /dev/null @@ -1,207 +0,0 @@ -# Scale Map Maker - -A web-based interactive mapping tool for creating and managing network diagrams with lines, nodes, and switches on a real map. - -## Getting Started - -Simply open `index.html` in your web browser. The application runs entirely in the browser and saves your work to localStorage automatically. - -## Map Navigation - -### Basic Movement -- **Left Click + Drag**: Pan the map (default) -- **Middle Mouse Button + Drag**: Pan the map -- **Right Click + Drag** (in Select mode): Pan the map -- **Mouse Wheel**: Zoom in/out -- **Escape Key**: Refresh the page - -### Map View -- Toggle between **Street View** and **Satellite View** using the "Satellite View" button in the toolbar -- Your current map position and zoom level are automatically saved - -## Tools - -### Select Tool -**Default tool** - Use this to view and interact with objects. - -- **Left Click on Node/Switch**: Opens a popup showing information -- **Right Click**: Pan the map -- Objects can be selected to view their properties - -### Move Tool -Move objects around the map. - -- **Left Click on Node/Switch**: Start dragging to move it -- **Right Click on Node/Switch**: Alternative way to move it -- All connected lines update automatically when you move nodes or switches -- Release mouse to finish moving - -### Delete Tool -Remove objects from the map. - -- **Left Click on Node**: Delete the node (and all connected lines) -- **Left Click on Switch**: Delete the switch (and all connected lines) -- **Left Click on Line Endpoint**: Delete that point (or entire line if only 2 points) -- All deletions require confirmation - -### Draw Line Tool -Create lines between points on the map. - -- **Left Click**: Add a point to the current line -- **Right Click**: Finish drawing the line -- **Click on Node/Switch**: Line snaps to and connects with that object -- **Click on Line Endpoint**: Continue drawing from that line -- Lines automatically finish when clicking on nodes/switches -- Lines that end near nodes/switches (within 20 pixels) automatically snap to them - -**Line Features:** -- Distance is calculated and displayed for each segment -- Total line distance shown at the midpoint -- Lines can have multiple points/segments -- Line endpoints can be moved in Move mode -- Right-click on endpoint to delete that point or the entire line - -### Add Node Tool -Add colored nodes to the map. - -- **Left Click**: Place a node at that location -- Nodes are connection points for lines -- Select node color from the color palette before placing -- Node count is tracked by color in the sidebar - -**Node Colors Available:** -- Red, Green, Blue, Yellow, Magenta, Cyan, Orange, Purple - -### Add Switch Tool -Add network switches to the map. - -- **Left Click**: Place a switch at that location -- Default: 8 ports per switch -- Switches can connect to lines (limited by port count) -- Port count can be edited after creation - -**Switch Features:** -- Click on switch to view popup with port information -- Edit port count using the input field (cannot reduce below used ports) -- Switches show used/total ports (e.g., "2/8") -- Lines automatically attach to available ports - -### Ruler Tool -Measure distances on the map. - -- **Left Click**: Add measurement points -- **Right Click**: Finish and reset measurement -- Distance shown in meters and kilometers in the top-right corner -- Useful for planning line routes - -### Sum Tool -Calculate total distance of selected line segments. - -- Click on individual line segments to select/deselect them -- Selected segments are highlighted -- Total distance shown in top-right corner -- Shows segment count and total distance in meters and kilometers -- Switching to another tool clears the selection - -## Map Statistics - -The **Total Stats** display (top-right corner) always shows: -- Number of lines -- Number of switches -- Total length of all lines (meters and kilometers) - -Updates automatically as you add/remove objects. - -## Node Count - -The **Node Count** section in the sidebar shows: -- Count of nodes by color -- Color indicator for each type -- Updates automatically - -## Data Management - -### Auto-Save -- All changes are automatically saved to browser localStorage -- Data persists between sessions -- Saves on: object creation/deletion, movement, map pan/zoom - -### Export -1. Click the **Export** button -2. Your data is encoded to a base64 string -3. String is copied to clipboard automatically -4. A prompt also shows the string for manual copying -5. Save this string to transfer your map to another browser/computer - -### Import -1. Click the **Import** button -2. Paste the export code when prompted -3. Page reloads with the imported data -4. All existing data is replaced - -### Clear All -- Click **Clear All** to delete everything -- Requires confirmation -- Cannot be undone (unless you have an export) - -## Tips and Tricks - -1. **Quick Refresh**: Press Escape to quickly reload the page -2. **Precise Connections**: Lines automatically snap to nearby nodes/switches when finishing -3. **Line Continuation**: Click on line endpoints to extend existing lines -4. **Port Management**: Switches prevent connections beyond port capacity -5. **Multi-Point Lines**: Create complex routes by clicking multiple points before finishing -6. **Batch Selection**: Use Sum tool to analyze multiple line segments at once -7. **Color Coding**: Use different node colors to categorize different types of connections -8. **Map Views**: Switch to satellite view for better geographic context - -## Keyboard Shortcuts - -- **Escape**: Refresh the page - -## Popup Interactions - -When a popup is open (node/switch info): -- Click inside popup to interact without triggering map actions -- Close popup by clicking the X or clicking elsewhere on the map -- Popups prevent accidental map clicks - -## Technical Details - -- Built with Leaflet.js for mapping -- Uses OpenStreetMap for street view -- Uses Google Maps for satellite view -- Max zoom level: 25 -- Data stored in browser localStorage -- No server required - runs entirely client-side - -## Browser Compatibility - -Works best in modern browsers: -- Chrome/Edge (recommended) -- Firefox -- Safari - -Requires: -- JavaScript enabled -- localStorage support -- Modern CSS support - -## Troubleshooting - -**Map not loading?** -- Check internet connection (map tiles load from remote servers) -- Try refreshing the page - -**Data not saving?** -- Ensure localStorage is enabled in browser settings -- Check if browser is in private/incognito mode (localStorage may not persist) - -**Import not working?** -- Ensure you copied the complete export string -- Check for extra spaces or characters -- Export string should be a long base64 encoded string - -**Objects disappearing?** -- Press Escape to refresh and reload from localStorage -- Try exporting data before making changes as a backup diff --git a/app.js b/app.js deleted file mode 100644 index 4c40699..0000000 --- a/app.js +++ /dev/null @@ -1,2003 +0,0 @@ -// Initialize map - will be set to saved position or F. Dharanboodhoo, Maldives -const saved = localStorage.getItem('mapMakerData'); -let initialView = [3.0667, 73.1833]; -let initialZoom = 15; -let initialIsSatellite = false; - -if (saved) { - const data = JSON.parse(saved); - if (data.mapView) { - initialView = [data.mapView.center.lat, data.mapView.center.lng]; - initialZoom = data.mapView.zoom; - initialIsSatellite = data.mapView.isSatellite || false; - } -} - -const map = L.map('map', { - maxZoom: 25, - dragging: false // Disable default left-click dragging -}).setView(initialView, initialZoom); - -// Create custom panes for proper z-index ordering -map.createPane('linesPane'); -map.createPane('segmentsPane'); -map.createPane('switchesPane'); -map.createPane('nodesPane'); - -// Set z-index for each pane (higher = on top) -map.getPane('linesPane').style.zIndex = 400; -map.getPane('segmentsPane').style.zIndex = 410; // Above lines, below switches -map.getPane('switchesPane').style.zIndex = 450; -map.getPane('nodesPane').style.zIndex = 500; - -// Enable middle mouse button and right-click dragging -let isPanning = false; -let panStart = null; - -map.getContainer().addEventListener('mousedown', (e) => { - // Middle mouse button OR right-click in select/move mode OR left-click in move mode - if (e.button === 1 || - (e.button === 2 && (currentTool === 'select' || currentTool === 'move')) || - (e.button === 0 && currentTool === 'move')) { - e.preventDefault(); - isPanning = true; - panStart = { x: e.clientX, y: e.clientY }; - map.getContainer().style.cursor = 'grabbing'; - } -}); - -map.getContainer().addEventListener('mousemove', (e) => { - if (isPanning && panStart) { - const dx = e.clientX - panStart.x; - const dy = e.clientY - panStart.y; - - const center = map.getCenter(); - const zoom = map.getZoom(); - const scale = 256 * Math.pow(2, zoom); - - const newCenter = map.unproject( - map.project(center, zoom).subtract([dx, dy]), - zoom - ); - - map.panTo(newCenter, { animate: false }); - panStart = { x: e.clientX, y: e.clientY }; - } -}); - -map.getContainer().addEventListener('mouseup', (e) => { - if (e.button === 1 || e.button === 2 || e.button === 0) { - isPanning = false; - panStart = null; - map.getContainer().style.cursor = ''; - } -}); - -map.getContainer().addEventListener('mouseleave', () => { - if (isPanning) { - isPanning = false; - panStart = null; - map.getContainer().style.cursor = ''; - } -}); - -// Base layers -const streetLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap contributors', - maxZoom: 25, - maxNativeZoom: 19 -}); - -const satelliteLayer = L.tileLayer('https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { - attribution: '© Google', - maxZoom: 25, - maxNativeZoom: 22 -}); - -// Start with saved view or street view -let isSatelliteView = initialIsSatellite; -if (isSatelliteView) { - satelliteLayer.addTo(map); -} else { - streetLayer.addTo(map); -} - -// Update button text when DOM is ready -window.addEventListener('DOMContentLoaded', () => { - if (isSatelliteView) { - document.getElementById('satelliteToggle').textContent = 'Street View'; - } -}); - -// State management -let currentTool = 'select'; -let currentColor = '#FF0000'; -let drawingLine = false; -let linePoints = []; -let rulerPoints = []; -let selectedObject = null; -let tempMarkers = []; -let selectedSegments = new Set(); // For sum tool -let sumDisplay = null; - -// Storage for all drawn objects -let drawnObjects = { - lines: [], - nodes: [], - switches: [] -}; - -// Layer groups -const layerGroup = L.layerGroup().addTo(map); - -// Tool button handlers -document.querySelectorAll('.tool-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active')); - e.target.classList.add('active'); - - const toolId = e.target.id; - if (toolId === 'lineTool') currentTool = 'line'; - else if (toolId === 'nodeTool') currentTool = 'node'; - else if (toolId === 'switchTool') currentTool = 'switch'; - else if (toolId === 'rulerTool') currentTool = 'ruler'; - else if (toolId === 'sumTool') currentTool = 'sum'; - else if (toolId === 'selectTool') currentTool = 'select'; - else if (toolId === 'moveTool') currentTool = 'move'; - else if (toolId === 'deleteTool') currentTool = 'delete'; - - // Reset temporary states - finishLineDrawing(); - rulerPoints = []; - document.getElementById('rulerDisplay').style.display = 'none'; - - // Clear sum selections when switching tools - if (toolId !== 'sumTool') { - clearSumSelections(); - // Hide sum display when switching away from sum tool - if (sumDisplay) { - sumDisplay.style.display = 'none'; - } - } else { - showSumDisplay(); - } - }); -}); - -// Satellite toggle handler -document.getElementById('satelliteToggle').addEventListener('click', () => { - if (isSatelliteView) { - map.removeLayer(satelliteLayer); - map.addLayer(streetLayer); - document.getElementById('satelliteToggle').textContent = 'Satellite View'; - isSatelliteView = false; - } else { - map.removeLayer(streetLayer); - map.addLayer(satelliteLayer); - document.getElementById('satelliteToggle').textContent = 'Street View'; - isSatelliteView = true; - } - saveToLocalStorage(); -}); - -// Color picker handlers -document.querySelectorAll('.color-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('active')); - e.target.classList.add('active'); - currentColor = e.target.dataset.color; - - // If a node is selected, change its color - if (selectedObject && selectedObject.type === 'node') { - changeNodeColor(selectedObject.data, currentColor); - } - }); -}); - -// Prevent popup clicks from triggering map clicks -map.on('popupopen', (e) => { - const popup = e.popup; - const container = popup.getElement(); - - if (container) { - container.addEventListener('click', (e) => { - e.stopPropagation(); - }); - container.addEventListener('mousedown', (e) => { - e.stopPropagation(); - }); - container.addEventListener('mouseup', (e) => { - e.stopPropagation(); - }); - } -}); - -// Map click handler -map.on('click', (e) => { - const latlng = e.latlng; - - if (currentTool === 'node') { - addNode(latlng, currentColor); - } else if (currentTool === 'switch') { - addSwitch(latlng); - } else if (currentTool === 'line') { - handleLineDrawing(latlng); - } else if (currentTool === 'ruler') { - handleRulerMeasurement(latlng); - } -}); - -// Map right-click to perform current tool action -map.on('contextmenu', (e) => { - L.DomEvent.preventDefault(e); - const latlng = e.latlng; - - if (currentTool === 'select' || currentTool === 'move') { - // Allow map panning with right-click in select and move mode - return; - } else if (currentTool === 'line') { - if (drawingLine) { - finishLineDrawing(); - } else { - handleLineDrawing(latlng); - } - } else if (currentTool === 'node') { - addNode(latlng, currentColor); - } else if (currentTool === 'switch') { - addSwitch(latlng); - } else if (currentTool === 'ruler') { - handleRulerMeasurement(latlng); - } -}); - -// Add node (now a point marker) -function addNode(latlng, color) { - const marker = L.circleMarker(latlng, { - radius: 6, - fillColor: color, - color: '#fff', - weight: 2, - fillOpacity: 1, - pane: 'nodesPane', - interactive: true - }).addTo(layerGroup); - - const nodeData = { - id: Date.now(), - latlng: latlng, - color: color, - layer: marker - }; - - // Track attached lines - marker._attachedLines = []; - marker._nodeData = nodeData; - - updateNodePopup(nodeData); - - marker.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'select') { - selectObject(nodeData, 'node'); - } else if (currentTool === 'move') { - // Start dragging the node - const startLatLng = marker.getLatLng(); - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - nodeData.latlng = newLatLng; - - // Update all attached lines - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else if (currentTool === 'line') { - // Use this node as a line point - handleLineDrawing(marker.getLatLng(), nodeData); - // Auto-finish the line when clicking on a node - if (drawingLine && linePoints.length >= 2) { - finishLineDrawing(); - } - } else if (currentTool === 'delete') { - // Delete the node - if (confirm('Are you sure you want to delete this node?')) { - // Remove all attached lines - if (marker._attachedLines && marker._attachedLines.length > 0) { - const linesToDelete = [...marker._attachedLines]; - linesToDelete.forEach(lineInfo => { - const lineData = lineInfo.line; - layerGroup.removeLayer(lineData.layer); - lineData.markers.forEach(m => layerGroup.removeLayer(m)); - lineData.labels.forEach(l => layerGroup.removeLayer(l)); - lineData.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id !== lineData.id); - }); - } - - // Remove the node - layerGroup.removeLayer(marker); - drawnObjects.nodes = drawnObjects.nodes.filter(n => n.id !== nodeData.id); - updateNodeCount(); - saveToLocalStorage(); - } - } - }); - - marker.on('contextmenu', (e) => { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - - if (currentTool === 'move') { - // Move the node with right-click in move mode - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - nodeData.latlng = newLatLng; - - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else { - // Delete the node in other modes - if (confirm('Are you sure you want to delete this node?')) { - layerGroup.removeLayer(marker); - drawnObjects.nodes = drawnObjects.nodes.filter(n => n.id !== nodeData.id); - if (selectedObject && selectedObject.data.id === nodeData.id) { - selectedObject = null; - } - updateNodeCount(); - saveToLocalStorage(); - } - } - }); - - // Make marker draggable in move mode - marker.options.draggable = false; - - drawnObjects.nodes.push(nodeData); - updateNodeCount(); - saveToLocalStorage(); -} - -function updateNodePopup(nodeData) { - const colorOptions = [ - '#FF0000', '#00FF00', '#0000FF', '#FFFF00', - '#FF00FF', '#00FFFF', '#FFA500', '#800080' - ]; - - const colorButtons = colorOptions.map(color => - `` - ).join(''); - - nodeData.layer.bindPopup(` -
- Node
-
- Change Color:
-
- ${colorButtons} -
-
- -
- `); -} - -function changeNodeColor(nodeData, newColor) { - nodeData.color = newColor; - nodeData.layer.setStyle({ fillColor: newColor }); - updateNodePopup(nodeData); - updateNodeCount(); - saveToLocalStorage(); -} - -// Add switch -function addSwitch(latlng) { - const ports = prompt("How many ports for this switch?", "8"); - if (!ports || isNaN(ports) || parseInt(ports) < 1) return; - - const portCount = parseInt(ports); - - const marker = L.marker(latlng, { - icon: L.divIcon({ - className: 'switch-marker', - html: `
${portCount}
`, - iconSize: [30, 30], - iconAnchor: [15, 15] - }), - pane: 'switchesPane' - }).addTo(layerGroup); - - const switchData = { - id: Date.now(), - latlng: latlng, - ports: portCount, - layer: marker - }; - - // Track attached lines - marker._attachedLines = []; - marker._switchData = switchData; - marker._maxPorts = portCount; - - updateSwitchPopup(switchData); - - marker.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'select') { - selectObject(switchData, 'switch'); - } else if (currentTool === 'move') { - // Close popup for move mode - marker.closePopup(); - - // Start dragging the switch - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - switchData.latlng = newLatLng; - - // Update all attached lines - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else if (currentTool === 'line') { - // Close popup for line mode - marker.closePopup(); - - // Check port limit - if (marker._attachedLines.length >= marker._maxPorts) { - alert(`This switch only has ${marker._maxPorts} ports and they are all in use!`); - return; - } - handleLineDrawing(marker.getLatLng(), switchData); - // Auto-finish the line when clicking on a switch - if (drawingLine && linePoints.length >= 2) { - finishLineDrawing(); - } - } else if (currentTool === 'delete') { - // Delete the switch - marker.closePopup(); - if (confirm('Are you sure you want to delete this switch?')) { - // Remove all attached lines - if (marker._attachedLines && marker._attachedLines.length > 0) { - const linesToDelete = [...marker._attachedLines]; - linesToDelete.forEach(lineInfo => { - const lineData = lineInfo.line; - layerGroup.removeLayer(lineData.layer); - lineData.markers.forEach(m => layerGroup.removeLayer(m)); - lineData.labels.forEach(l => layerGroup.removeLayer(l)); - lineData.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id !== lineData.id); - }); - } - - // Remove the switch - layerGroup.removeLayer(marker); - drawnObjects.switches = drawnObjects.switches.filter(s => s.id !== switchData.id); - saveToLocalStorage(); - } - } - }); - - marker.on('contextmenu', (e) => { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - - if (currentTool === 'move') { - // Move the switch with right-click in move mode - marker.closePopup(); - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - switchData.latlng = newLatLng; - - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else { - // Delete the switch in other modes - if (confirm('Are you sure you want to delete this switch?')) { - layerGroup.removeLayer(marker); - drawnObjects.switches = drawnObjects.switches.filter(s => s.id !== switchData.id); - if (selectedObject && selectedObject.data.id === switchData.id) { - selectedObject = null; - } - saveToLocalStorage(); - } - } - }); - - drawnObjects.switches.push(switchData); - saveToLocalStorage(); -} - -function updateSwitchPopup(switchData) { - const usedPorts = switchData.layer._attachedLines ? switchData.layer._attachedLines.length : 0; - - switchData.layer.bindPopup(` -
- Network Switch
-
- Ports: - -
- Used: ${usedPorts} / ${switchData.ports}
- Available: ${switchData.ports - usedPorts}
- -
- `); -} - -// Global function for popup buttons -window.changeNodeColorFromPopup = function(nodeId, color) { - const node = drawnObjects.nodes.find(n => n.id == nodeId); - if (node) { - changeNodeColor(node, color); - } -}; - -window.updateSwitchPorts = function(switchId) { - const switchObj = drawnObjects.switches.find(s => s.id == switchId); - if (!switchObj) return; - - const newPorts = parseInt(document.getElementById(`editPorts_${switchId}`).value); - const usedPorts = switchObj.layer._attachedLines ? switchObj.layer._attachedLines.length : 0; - - if (isNaN(newPorts) || newPorts < 1) { - alert("Switch must have at least 1 port."); - return; - } - - if (newPorts < usedPorts) { - alert(`Cannot set ports to ${newPorts}. ${usedPorts} ports are currently in use.`); - return; - } - - switchObj.ports = newPorts; - switchObj.layer._maxPorts = newPorts; - - // Update icon - switchObj.layer.setIcon(L.divIcon({ - className: 'switch-marker', - html: `
${newPorts}
`, - iconSize: [30, 30], - iconAnchor: [15, 15] - })); - - updateSwitchPopup(switchObj); - saveToLocalStorage(); -}; - -window.deleteObject = function(objId, type) { - if (!confirm(`Are you sure you want to delete this ${type}?`)) { - return; - } - - if (type === 'node') { - const node = drawnObjects.nodes.find(n => n.id == objId); - if (node) { - layerGroup.removeLayer(node.layer); - drawnObjects.nodes = drawnObjects.nodes.filter(n => n.id != objId); - updateNodeCount(); - saveToLocalStorage(); - } - } else if (type === 'switch') { - const switchObj = drawnObjects.switches.find(s => s.id == objId); - if (switchObj) { - layerGroup.removeLayer(switchObj.layer); - drawnObjects.switches = drawnObjects.switches.filter(s => s.id != objId); - saveToLocalStorage(); - } - } else if (type === 'line') { - const line = drawnObjects.lines.find(l => l.id == objId); - if (line) { - layerGroup.removeLayer(line.layer); - line.markers.forEach(m => { - // Remove from attached nodes/switches - if (m._attachedNode && m._attachedNode.layer) { - m._attachedNode.layer._attachedLines = - m._attachedNode.layer._attachedLines.filter(l => l.line.id !== line.id); - if (m._attachedNode.layer._switchData) { - updateSwitchPopup(m._attachedNode.layer._switchData); - } - } - layerGroup.removeLayer(m); - }); - line.labels.forEach(l => layerGroup.removeLayer(l)); - line.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id != objId); - saveToLocalStorage(); - } - } - - if (selectedObject && selectedObject.data.id == objId) { - selectedObject = null; - } -}; - -// Handle multi-segment line drawing -let attachedNodes = []; // Track which points are attached to nodes - -function handleLineDrawing(latlng, nodeData = null) { - if (!drawingLine) { - // Start new line - drawingLine = true; - linePoints = [latlng]; - attachedNodes = [nodeData]; // Store node reference (can be null) - tempMarkers = []; - - // Create start marker - const marker = L.circleMarker(latlng, { - radius: 5, - fillColor: '#3498db', - color: '#fff', - weight: 2, - fillOpacity: 1, - draggable: false - }).addTo(layerGroup); - - tempMarkers.push(marker); - } else { - // Add point to line - linePoints.push(latlng); - attachedNodes.push(nodeData); // Store node reference for this point - - // Create marker for this point - const marker = L.circleMarker(latlng, { - radius: 5, - fillColor: '#3498db', - color: '#fff', - weight: 2, - fillOpacity: 1, - draggable: false - }).addTo(layerGroup); - - tempMarkers.push(marker); - - // Update or create temporary polyline - if (window.tempPolyline) { - layerGroup.removeLayer(window.tempPolyline); - } - - window.tempPolyline = L.polyline(linePoints, { - color: '#3498db', - weight: 3, - opacity: 0.7 - }).addTo(layerGroup); - } -} - -// Helper function to find nearby node or switch and snap to it -function findNearbyObject(latlng, snapDistance = 20) { - const point = map.latLngToContainerPoint(latlng); - - // Check nodes - for (const node of drawnObjects.nodes) { - const nodePoint = map.latLngToContainerPoint(node.latlng); - const distance = Math.sqrt( - Math.pow(point.x - nodePoint.x, 2) + - Math.pow(point.y - nodePoint.y, 2) - ); - if (distance <= snapDistance) { - return { type: 'node', object: node, latlng: node.latlng }; - } - } - - // Check switches - for (const switchObj of drawnObjects.switches) { - const switchPoint = map.latLngToContainerPoint(switchObj.latlng); - const distance = Math.sqrt( - Math.pow(point.x - switchPoint.x, 2) + - Math.pow(point.y - switchPoint.y, 2) - ); - if (distance <= snapDistance) { - return { type: 'switch', object: switchObj, latlng: switchObj.latlng }; - } - } - - return null; -} - -function finishLineDrawing() { - if (!drawingLine || linePoints.length < 2) { - // Clean up if we didn't make a valid line - tempMarkers.forEach(m => layerGroup.removeLayer(m)); - if (window.tempPolyline) { - layerGroup.removeLayer(window.tempPolyline); - window.tempPolyline = null; - } - tempMarkers = []; - linePoints = []; - attachedNodes = []; - drawingLine = false; - window.extendingLine = null; - return; - } - - // Remove temporary markers and polyline - tempMarkers.forEach(m => layerGroup.removeLayer(m)); - if (window.tempPolyline) { - layerGroup.removeLayer(window.tempPolyline); - window.tempPolyline = null; - } - tempMarkers = []; - - // Snap endpoints to nearby nodes or switches - const firstPoint = linePoints[0]; - const lastPoint = linePoints[linePoints.length - 1]; - - const nearbyFirst = findNearbyObject(firstPoint); - if (nearbyFirst && !attachedNodes[0]) { - linePoints[0] = nearbyFirst.latlng; - if (nearbyFirst.type === 'node') { - attachedNodes[0] = nearbyFirst.object; - } else if (nearbyFirst.type === 'switch') { - // Attach to switch - const usedPorts = nearbyFirst.object.layer._attachedLines ? nearbyFirst.object.layer._attachedLines.length : 0; - if (usedPorts < nearbyFirst.object.ports) { - attachedNodes[0] = nearbyFirst.object; - } - } - } - - const nearbyLast = findNearbyObject(lastPoint); - const lastIndex = linePoints.length - 1; - if (nearbyLast && !attachedNodes[lastIndex]) { - linePoints[lastIndex] = nearbyLast.latlng; - if (nearbyLast.type === 'node') { - attachedNodes[lastIndex] = nearbyLast.object; - } else if (nearbyLast.type === 'switch') { - // Attach to switch - const usedPorts = nearbyLast.object.layer._attachedLines ? nearbyLast.object.layer._attachedLines.length : 0; - if (usedPorts < nearbyLast.ports) { - attachedNodes[lastIndex] = nearbyLast.object; - } - } - } - - // Check if we're extending an existing line - if (window.extendingLine) { - const { lineData, fromStart } = window.extendingLine; - - // Remove the old line - layerGroup.removeLayer(lineData.layer); - lineData.markers.forEach(m => { - if (m._attachedNode && m._attachedNode.layer) { - m._attachedNode.layer._attachedLines = - m._attachedNode.layer._attachedLines.filter(l => l.line.id !== lineData.id); - } - layerGroup.removeLayer(m); - }); - lineData.labels.forEach(l => layerGroup.removeLayer(l)); - lineData.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id !== lineData.id); - - // Create new line with extended points - if (fromStart) { - // Reverse the new points and prepend to existing - linePoints.reverse(); - attachedNodes.reverse(); - } - - createEditableLine(linePoints, attachedNodes); - window.extendingLine = null; - } else { - // Create the final line with draggable endpoints - createEditableLine(linePoints, attachedNodes); - } - - // Reset state - linePoints = []; - attachedNodes = []; - drawingLine = false; -} - -function createEditableLine(points, nodeAttachments = []) { - const polyline = L.polyline(points, { - color: '#3498db', - weight: 3, - pane: 'linesPane' - }).addTo(layerGroup); - - // Calculate total distance - let totalDistance = 0; - for (let i = 0; i < points.length - 1; i++) { - totalDistance += map.distance(points[i], points[i + 1]); - } - - const distanceKm = (totalDistance / 1000).toFixed(2); - const distanceM = totalDistance.toFixed(2); - - // Create draggable endpoint markers - const markers = points.map((point, index) => { - const attachedNode = nodeAttachments[index]; - - const marker = L.marker(point, { - draggable: false, - icon: L.divIcon({ - className: 'endpoint-marker', - html: '
', - iconSize: [12, 12], - iconAnchor: [6, 6] - }), - pane: 'linesPane' - }).addTo(layerGroup); - - // Store reference to line data and attached node - marker._lineData = null; // Will be set after lineData is created - marker._attachedNode = attachedNode; - - marker.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'move') { - marker.dragging.enable(); - } else if (currentTool === 'line') { - // Continue the line from this endpoint - const lineData = marker._lineData; - if (!lineData) return; - - // Check if this is the first or last point - const isFirstPoint = index === 0; - const isLastPoint = index === lineData.points.length - 1; - - if (isFirstPoint || isLastPoint) { - // Start drawing from this endpoint - drawingLine = true; - linePoints = [...lineData.points]; - attachedNodes = lineData.markers.map(m => m._attachedNode); - tempMarkers = []; - - // Store the line we're extending - window.extendingLine = { - lineData: lineData, - fromStart: isFirstPoint - }; - } - } else if (currentTool === 'delete') { - // Delete the line endpoint (and entire line if only 2 points) - const lineData = marker._lineData; - if (!lineData) return; - - if (lineData.points.length === 2) { - if (confirm('Are you sure you want to delete this line?')) { - layerGroup.removeLayer(lineData.layer); - lineData.markers.forEach(m => { - if (m._attachedNode && m._attachedNode.layer) { - m._attachedNode.layer._attachedLines = - m._attachedNode.layer._attachedLines.filter(l => l.line.id !== lineData.id); - } - layerGroup.removeLayer(m); - }); - lineData.labels.forEach(l => layerGroup.removeLayer(l)); - lineData.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id !== lineData.id); - saveToLocalStorage(); - } - } else { - if (confirm('Are you sure you want to delete this line point?')) { - // Remove from attached node if exists - if (marker._attachedNode && marker._attachedNode.layer) { - marker._attachedNode.layer._attachedLines = - marker._attachedNode.layer._attachedLines.filter( - info => info.line.id !== lineData.id || info.pointIndex !== index - ); - } - - lineData.points.splice(index, 1); - layerGroup.removeLayer(marker); - lineData.markers.splice(index, 1); - lineData.layer.setLatLngs(lineData.points); - - // Update all marker references - lineData.markers.forEach((m, newIndex) => { - m._lineData = lineData; - if (m._attachedNode && m._attachedNode.layer) { - m._attachedNode.layer._attachedLines = - m._attachedNode.layer._attachedLines.map(info => { - if (info.line.id === lineData.id) { - return { ...info, pointIndex: newIndex }; - } - return info; - }); - } - }); - - updateLineLabels(lineData); - saveToLocalStorage(); - } - } - } - }); - - marker.on('contextmenu', (e) => { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - - const lineData = marker._lineData; - if (!lineData) return; - - // Remove from attached node if exists - if (marker._attachedNode && marker._attachedNode.layer) { - marker._attachedNode.layer._attachedLines = - marker._attachedNode.layer._attachedLines.filter( - info => info.line.id !== lineData.id || info.pointIndex !== index - ); - } - - // If line has only 2 points, delete the entire line - if (lineData.points.length === 2) { - if (confirm('Are you sure you want to delete this line?')) { - layerGroup.removeLayer(lineData.layer); - lineData.markers.forEach(m => { - if (m._attachedNode && m._attachedNode.layer) { - m._attachedNode.layer._attachedLines = - m._attachedNode.layer._attachedLines.filter(l => l.line.id !== lineData.id); - } - layerGroup.removeLayer(m); - }); - lineData.labels.forEach(l => layerGroup.removeLayer(l)); - lineData.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id !== lineData.id); - saveToLocalStorage(); - } - } else { - // Remove this point from the line - if (confirm('Are you sure you want to delete this line point?')) { - lineData.points.splice(index, 1); - - // Remove this marker - layerGroup.removeLayer(marker); - lineData.markers.splice(index, 1); - - // Update the line - lineData.layer.setLatLngs(lineData.points); - - // Update all marker references - lineData.markers.forEach((m, newIndex) => { - m._lineData = lineData; - // Update attached node references with new index - if (m._attachedNode && m._attachedNode.layer) { - m._attachedNode.layer._attachedLines = - m._attachedNode.layer._attachedLines.map(info => { - if (info.line.id === lineData.id) { - return { ...info, pointIndex: newIndex }; - } - return info; - }); - } - }); - - updateLineLabels(lineData); - saveToLocalStorage(); - } - } - }); - - // Update line when marker is dragged - marker.on('drag', () => { - const lineData = marker._lineData; - if (lineData) { - lineData.points[index] = marker.getLatLng(); - polyline.setLatLngs(lineData.points); - updateLineLabels(lineData); - } - }); - - marker.on('dragend', () => { - marker.dragging.disable(); - saveToLocalStorage(); - }); - - return marker; - }); - - // Add distance labels for each segment - const labels = []; - const segments = []; - for (let i = 0; i < points.length - 1; i++) { - const segmentDistance = map.distance(points[i], points[i + 1]); - const segmentM = segmentDistance.toFixed(2); - - const midpoint = L.latLng( - (points[i].lat + points[i + 1].lat) / 2, - (points[i].lng + points[i + 1].lng) / 2 - ); - - const label = L.marker(midpoint, { - icon: L.divIcon({ - className: 'distance-label', - html: `${segmentM}m`, - iconSize: null - }), - interactive: true, - opacity: 0 - }).addTo(layerGroup); - - // Create segment polyline for sum tool (make it wider for easier hovering) - const segmentLine = L.polyline([points[i], points[i + 1]], { - color: '#3498db', - weight: 10, - opacity: 0, - interactive: true, - pane: 'segmentsPane' - }).addTo(layerGroup); - - const segmentData = { - index: i, - distance: segmentDistance, - layer: segmentLine, - points: [points[i], points[i + 1]], - label: label - }; - - // Add hover handlers to show/hide label - segmentLine.on('mouseover', (e) => { - label.setOpacity(1); - }); - - segmentLine.on('mouseout', (e) => { - label.setOpacity(0); - }); - - // Add click handler to the segment line - segmentLine.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'sum') { - toggleSegmentSelection(lineData, segmentData); - } else if (currentTool === 'select') { - selectObject(lineData, 'line'); - } - }); - - // Add click handler to the label as well - label.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'sum') { - toggleSegmentSelection(lineData, segmentData); - } else if (currentTool === 'select') { - selectObject(lineData, 'line'); - } - }); - - // Keep label visible when hovering over it - label.on('mouseover', (e) => { - label.setOpacity(1); - }); - - label.on('mouseout', (e) => { - label.setOpacity(0); - }); - - segments.push(segmentData); - labels.push(label); - } - - const lineData = { - id: Date.now(), - points: points, - distance: totalDistance, - layer: polyline, - markers: markers, - labels: labels, - segments: segments - }; - - // Set line data reference in markers and register with attached nodes - markers.forEach((m, i) => { - m._lineData = lineData; - - // If this marker is attached to a node, register the line with that node - if (m._attachedNode && m._attachedNode.layer) { - if (!m._attachedNode.layer._attachedLines) { - m._attachedNode.layer._attachedLines = []; - } - m._attachedNode.layer._attachedLines.push({ - line: lineData, - pointIndex: i - }); - // Update switch popup if attached to a switch - if (m._attachedNode.layer._switchData) { - updateSwitchPopup(m._attachedNode.layer._switchData); - } - } - }); - - polyline.bindPopup(` -
- Line
- Total Distance: ${distanceM}m
- ${distanceKm}km
- Segments: ${points.length - 1}
- -
- `); - - polyline.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'select') { - selectObject(lineData, 'line'); - } - }); - - polyline.on('contextmenu', (e) => { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - // Delete the line - if (confirm('Are you sure you want to delete this line?')) { - layerGroup.removeLayer(polyline); - lineData.markers.forEach(m => { - // Remove from attached nodes - const attachedNode = m._attachedNode; - if (attachedNode) { - attachedNode._attachedLines = attachedNode._attachedLines.filter(l => l !== lineData); - } - layerGroup.removeLayer(m); - }); - lineData.labels.forEach(l => layerGroup.removeLayer(l)); - lineData.segments.forEach(s => layerGroup.removeLayer(s.layer)); - drawnObjects.lines = drawnObjects.lines.filter(l => l.id !== lineData.id); - if (selectedObject && selectedObject.data.id === lineData.id) { - selectedObject = null; - } - saveToLocalStorage(); - } - }); - - // Make polyline draggable in move mode - polyline.on('mousedown', (e) => { - if (currentTool === 'move') { - L.DomEvent.stopPropagation(e); - const startLatLng = e.latlng; - const originalPoints = lineData.points.map(p => ({lat: p.lat, lng: p.lng})); - - const onMouseMove = (e) => { - const latDiff = e.latlng.lat - startLatLng.lat; - const lngDiff = e.latlng.lng - startLatLng.lng; - - lineData.points = originalPoints.map(p => - L.latLng(p.lat + latDiff, p.lng + lngDiff) - ); - - polyline.setLatLngs(lineData.points); - lineData.markers.forEach((marker, i) => { - marker.setLatLng(lineData.points[i]); - }); - updateLineLabels(lineData); - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - saveToLocalStorage(); - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } - }); - - drawnObjects.lines.push(lineData); - saveToLocalStorage(); -} - -function updateLineLabels(lineData) { - const points = lineData.points; - - // Remove old labels and segments - lineData.labels.forEach(label => layerGroup.removeLayer(label)); - lineData.labels = []; - - if (lineData.segments) { - lineData.segments.forEach(seg => layerGroup.removeLayer(seg.layer)); - } - lineData.segments = []; - - // Calculate new total distance - let totalDistance = 0; - for (let i = 0; i < points.length - 1; i++) { - totalDistance += map.distance(points[i], points[i + 1]); - } - lineData.distance = totalDistance; - - // Create new labels and segments - for (let i = 0; i < points.length - 1; i++) { - const segmentDistance = map.distance(points[i], points[i + 1]); - const segmentM = segmentDistance.toFixed(2); - - const midpoint = L.latLng( - (points[i].lat + points[i + 1].lat) / 2, - (points[i].lng + points[i + 1].lng) / 2 - ); - - const label = L.marker(midpoint, { - icon: L.divIcon({ - className: 'distance-label', - html: `${segmentM}m`, - iconSize: null - }), - interactive: true, - opacity: 0 - }).addTo(layerGroup); - - // Recreate segment polyline for sum tool (make it wider for easier hovering) - const segmentLine = L.polyline([points[i], points[i + 1]], { - color: '#3498db', - weight: 10, - opacity: 0, - interactive: true, - pane: 'segmentsPane' - }).addTo(layerGroup); - - const segmentData = { - index: i, - distance: segmentDistance, - layer: segmentLine, - points: [points[i], points[i + 1]], - label: label - }; - - // Add hover handlers to show/hide label - segmentLine.on('mouseover', (e) => { - label.setOpacity(1); - }); - - segmentLine.on('mouseout', (e) => { - label.setOpacity(0); - }); - - // Add click handler to the segment line - segmentLine.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'sum') { - toggleSegmentSelection(lineData, segmentData); - } else if (currentTool === 'select') { - selectObject(lineData, 'line'); - } - }); - - // Add click handler to the label as well - label.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'sum') { - toggleSegmentSelection(lineData, segmentData); - } else if (currentTool === 'select') { - selectObject(lineData, 'line'); - } - }); - - // Keep label visible when hovering over it - label.on('mouseover', (e) => { - label.setOpacity(1); - }); - - label.on('mouseout', (e) => { - label.setOpacity(0); - }); - - lineData.segments.push(segmentData); - lineData.labels.push(label); - } - - // Update popup - const distanceKm = (totalDistance / 1000).toFixed(2); - const distanceM = totalDistance.toFixed(2); - lineData.layer.setPopupContent(` -
- Line
- Total Distance: ${distanceM}m
- ${distanceKm}km
- Segments: ${points.length - 1}
- -
- `); -} - -// Handle ruler measurement -function handleRulerMeasurement(latlng) { - rulerPoints.push(latlng); - - if (rulerPoints.length === 1) { - document.getElementById('rulerDisplay').style.display = 'block'; - document.getElementById('rulerDisplay').textContent = 'Click second point...'; - } else if (rulerPoints.length === 2) { - const distance = map.distance(rulerPoints[0], rulerPoints[1]); - const distanceKm = (distance / 1000).toFixed(2); - const distanceM = distance.toFixed(2); - - document.getElementById('rulerDisplay').innerHTML = ` - Distance: ${distanceM}m
- ${distanceKm}km - `; - - const tempLine = L.polyline(rulerPoints, { - color: '#f39c12', - weight: 2, - dashArray: '5, 10' - }).addTo(layerGroup); - - setTimeout(() => { - layerGroup.removeLayer(tempLine); - rulerPoints = []; - document.getElementById('rulerDisplay').style.display = 'none'; - }, 3000); - } -} - -// Sum tool functions -function toggleSegmentSelection(lineData, segmentData) { - const segmentId = `${lineData.id}-${segmentData.index}`; - - if (selectedSegments.has(segmentId)) { - // Deselect - selectedSegments.delete(segmentId); - segmentData.layer.setStyle({ opacity: 0, weight: 3 }); - } else { - // Select - selectedSegments.add(segmentId); - segmentData.layer.setStyle({ opacity: 0.5, weight: 5, color: '#27ae60' }); - } - - updateSumDisplay(); -} - -function updateSumDisplay() { - if (!sumDisplay) return; - - let totalDistance = 0; - drawnObjects.lines.forEach(line => { - line.segments.forEach(segment => { - const segmentId = `${line.id}-${segment.index}`; - if (selectedSegments.has(segmentId)) { - totalDistance += segment.distance; - } - }); - }); - - const distanceKm = (totalDistance / 1000).toFixed(2); - const distanceM = totalDistance.toFixed(2); - const count = selectedSegments.size; - - sumDisplay.innerHTML = ` - Sum Tool
- Segments: ${count}
- Total: ${distanceM}m
- ${distanceKm}km - `; -} - -function showSumDisplay() { - if (!sumDisplay) { - sumDisplay = document.getElementById('sumDisplay'); - } - sumDisplay.style.display = 'block'; - updateSumDisplay(); -} - -function clearSumSelections() { - drawnObjects.lines.forEach(line => { - if (line.segments) { - line.segments.forEach(segment => { - segment.layer.setStyle({ opacity: 0, weight: 3, color: '#3498db' }); - }); - } - }); - selectedSegments.clear(); - if (sumDisplay) { - sumDisplay.style.display = 'none'; - } -} - -// Select object -function selectObject(objData, type) { - if (selectedObject) { - // Deselect previous - if (selectedObject.type === 'node') { - selectedObject.data.layer.setStyle({ weight: 2 }); - } else if (selectedObject.type === 'line') { - selectedObject.data.layer.setStyle({ weight: 3 }); - } - } - - selectedObject = { data: objData, type: type }; - - // Highlight selected - if (type === 'node') { - objData.layer.setStyle({ weight: 4 }); - } else if (type === 'line') { - objData.layer.setStyle({ weight: 5 }); - } -} - -// Clear all -// Export button - copy base64 encoded localStorage to clipboard -document.getElementById('exportBtn').addEventListener('click', () => { - const data = localStorage.getItem('mapMakerData'); - console.log('Exporting data:', data); - if (!data) { - alert('No data to export!'); - return; - } - const base64 = btoa(data); - console.log('Base64 encoded:', base64); - - // Try to copy to clipboard - navigator.clipboard.writeText(base64).then(() => { - // Show in prompt as well so user can manually copy if needed - prompt('Data copied to clipboard! You can also copy it manually from here:', base64); - }).catch((err) => { - console.error('Clipboard error:', err); - // Fallback if clipboard API fails - prompt('Copy this export code:', base64); - }); -}); - -// Import button - prompt for base64 string and load it -document.getElementById('importBtn').addEventListener('click', () => { - const base64 = prompt('Paste the export code here:'); - console.log('Import base64:', base64); - if (!base64 || base64.trim() === '') { - console.log('Import cancelled - no data'); - return; - } - - try { - const data = atob(base64.trim()); - console.log('Decoded data:', data); - - // Validate it's valid JSON - const parsed = JSON.parse(data); - console.log('Parsed data:', parsed); - - // Set a flag to prevent beforeunload from saving - window.importingData = true; - - // Clear existing data and set new data - console.log('Clearing localStorage...'); - localStorage.clear(); - console.log('Setting new data...'); - localStorage.setItem('mapMakerData', data); - console.log('Data saved, current localStorage:', localStorage.getItem('mapMakerData')); - - alert('Data imported successfully! The page will now reload.'); - location.reload(); - } catch (e) { - console.error('Import error:', e); - alert('Invalid import code! Please make sure you copied it correctly. Error: ' + e.message); - } -}); - -document.getElementById('clearBtn').addEventListener('click', () => { - if (confirm('Are you sure you want to clear all objects?')) { - layerGroup.clearLayers(); - drawnObjects = { lines: [], nodes: [], switches: [] }; - selectedObject = null; - updateNodeCount(); - saveToLocalStorage(); - } -}); - -// Update node count display -function updateNodeCount() { - const counts = {}; - drawnObjects.nodes.forEach(node => { - counts[node.color] = (counts[node.color] || 0) + 1; - }); - - const countHtml = Object.entries(counts).map(([color, count]) => ` -
-
-
${count}
-
- `).join(''); - - document.getElementById('nodeCount').innerHTML = countHtml || '
No nodes
'; -} - -// Update stats display -function updateStatsDisplay() { - const statsDisplay = document.getElementById('statsDisplay'); - if (!statsDisplay) return; - - // Calculate total distance of all lines - let totalDistance = 0; - drawnObjects.lines.forEach(line => { - totalDistance += line.distance || 0; - }); - - const distanceKm = (totalDistance / 1000).toFixed(2); - const distanceM = totalDistance.toFixed(2); - const switchCount = drawnObjects.switches.length; - const lineCount = drawnObjects.lines.length; - - statsDisplay.innerHTML = ` - Total Stats
- Lines: ${lineCount}
- Switches: ${switchCount}
- Total Length: ${distanceM}m
- (${distanceKm}km) - `; -} - -// LocalStorage functions -function saveToLocalStorage() { - const data = { - lines: drawnObjects.lines.map(l => ({ - id: l.id, - points: l.points.map(p => [p.lat, p.lng]), - distance: l.distance - })), - nodes: drawnObjects.nodes.map(n => ({ - id: n.id, - latlng: [n.latlng.lat, n.latlng.lng], - color: n.color - })), - switches: drawnObjects.switches.map(s => ({ - id: s.id, - latlng: [s.latlng.lat, s.latlng.lng], - ports: s.ports - })), - mapView: { - center: map.getCenter(), - zoom: map.getZoom(), - isSatellite: isSatelliteView - } - }; - - localStorage.setItem('mapMakerData', JSON.stringify(data)); - updateStatsDisplay(); -} - -// Helper function to restore a node -function restoreNode(nodeData, latlng) { - const marker = L.circleMarker(latlng, { - radius: 6, - fillColor: nodeData.color, - color: '#fff', - weight: 2, - fillOpacity: 1, - pane: 'nodesPane', - interactive: true - }).addTo(layerGroup); - - const restoredNode = { - id: nodeData.id, - latlng: latlng, - color: nodeData.color, - layer: marker - }; - - marker._attachedLines = []; - marker._nodeData = restoredNode; - - updateNodePopup(restoredNode); - - marker.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'select') { - selectObject(restoredNode, 'node'); - } else if (currentTool === 'move') { - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - restoredNode.latlng = newLatLng; - - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else if (currentTool === 'line') { - handleLineDrawing(marker.getLatLng(), restoredNode); - // Auto-finish the line when clicking on a node - if (drawingLine && linePoints.length >= 2) { - finishLineDrawing(); - } - } - }); - - marker.on('contextmenu', (e) => { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - - if (currentTool === 'move') { - // Move the node with right-click in move mode - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - restoredNode.latlng = newLatLng; - - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else { - // Delete the node in other modes - if (confirm('Are you sure you want to delete this node?')) { - layerGroup.removeLayer(marker); - drawnObjects.nodes = drawnObjects.nodes.filter(n => n.id !== restoredNode.id); - if (selectedObject && selectedObject.data.id === restoredNode.id) { - selectedObject = null; - } - updateNodeCount(); - saveToLocalStorage(); - } - } - }); - - drawnObjects.nodes.push(restoredNode); -} - -// Helper function to restore a switch -function restoreSwitch(switchData, latlng) { - const marker = L.marker(latlng, { - icon: L.divIcon({ - className: 'switch-marker', - html: `
${switchData.ports}
`, - iconSize: [30, 30], - iconAnchor: [15, 15] - }), - pane: 'switchesPane' - }).addTo(layerGroup); - - const restoredSwitch = { - id: switchData.id, - latlng: latlng, - ports: switchData.ports, - layer: marker - }; - - marker._attachedLines = []; - marker._switchData = restoredSwitch; - marker._maxPorts = switchData.ports; - - updateSwitchPopup(restoredSwitch); - - marker.on('click', (e) => { - L.DomEvent.stopPropagation(e); - if (currentTool === 'select') { - selectObject(restoredSwitch, 'switch'); - } else if (currentTool === 'move') { - // Close popup for move mode - marker.closePopup(); - - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - restoredSwitch.latlng = newLatLng; - - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else if (currentTool === 'line') { - // Close popup for line mode - marker.closePopup(); - - if (marker._attachedLines.length >= marker._maxPorts) { - alert(`This switch only has ${marker._maxPorts} ports and they are all in use!`); - return; - } - handleLineDrawing(marker.getLatLng(), restoredSwitch); - // Auto-finish the line when clicking on a switch - if (drawingLine && linePoints.length >= 2) { - finishLineDrawing(); - } - } - }); - - marker.on('contextmenu', (e) => { - L.DomEvent.stopPropagation(e); - L.DomEvent.preventDefault(e); - - if (currentTool === 'move') { - // Move the switch with right-click in move mode - marker.closePopup(); - let isDragging = false; - - const onMouseMove = (e) => { - isDragging = true; - const newLatLng = e.latlng; - marker.setLatLng(newLatLng); - restoredSwitch.latlng = newLatLng; - - if (marker._attachedLines) { - marker._attachedLines.forEach(lineInfo => { - const { line, pointIndex } = lineInfo; - line.points[pointIndex] = newLatLng; - line.layer.setLatLngs(line.points); - line.markers[pointIndex].setLatLng(newLatLng); - updateLineLabels(line); - }); - } - }; - - const onMouseUp = () => { - map.off('mousemove', onMouseMove); - map.off('mouseup', onMouseUp); - if (isDragging) { - saveToLocalStorage(); - } - }; - - map.on('mousemove', onMouseMove); - map.on('mouseup', onMouseUp); - } else { - // Delete the switch in other modes - if (confirm('Are you sure you want to delete this switch?')) { - layerGroup.removeLayer(marker); - drawnObjects.switches = drawnObjects.switches.filter(s => s.id !== restoredSwitch.id); - if (selectedObject && selectedObject.data.id === restoredSwitch.id) { - selectedObject = null; - } - saveToLocalStorage(); - } - } - }); - - drawnObjects.switches.push(restoredSwitch); -} - -function loadFromLocalStorage() { - const saved = localStorage.getItem('mapMakerData'); - if (!saved) return; - - const data = JSON.parse(saved); - - // Map view already restored during initialization - - // Restore nodes - if (data.nodes) { - data.nodes.forEach(nodeData => { - const latlng = L.latLng(nodeData.latlng[0], nodeData.latlng[1]); - restoreNode(nodeData, latlng); - }); - updateNodeCount(); - } - - // Restore switches - if (data.switches) { - data.switches.forEach(switchData => { - const latlng = L.latLng(switchData.latlng[0], switchData.latlng[1]); - restoreSwitch(switchData, latlng); - }); - } - - // Restore lines - if (data.lines) { - data.lines.forEach(lineData => { - const points = lineData.points.map(p => L.latLng(p[0], p[1])); - createEditableLine(points); - }); - } -} - -// Auto-save on map move -map.on('moveend', saveToLocalStorage); -map.on('zoomend', saveToLocalStorage); - -// Load saved data on startup -loadFromLocalStorage(); - -// Initialize stats display -updateStatsDisplay(); - -// Ensure data is saved before page unload (refresh, close, navigate away) -window.addEventListener('beforeunload', (e) => { - // Don't save if we're importing data (would overwrite the imported data) - if (window.importingData) { - return; - } - saveToLocalStorage(); -}); - -// Refresh on Escape key -document.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - location.reload(); - } -}, true); - -// Mobile sidebar toggle functionality -document.addEventListener('DOMContentLoaded', () => { - const sidebarToggle = document.getElementById('sidebarToggle'); - const toolbar = document.querySelector('.toolbar'); - const overlay = document.getElementById('sidebarOverlay'); - - function toggleSidebar() { - toolbar.classList.toggle('open'); - overlay.classList.toggle('show'); - sidebarToggle.classList.toggle('hidden'); - } - - function closeSidebar() { - toolbar.classList.remove('open'); - overlay.classList.remove('show'); - sidebarToggle.classList.remove('hidden'); - } - - // Toggle sidebar when button is clicked - sidebarToggle.addEventListener('click', toggleSidebar); - - // Close sidebar when overlay is clicked - overlay.addEventListener('click', closeSidebar); - - // Close sidebar when escape key is pressed (on mobile) - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && window.innerWidth <= 768 && toolbar.classList.contains('open')) { - closeSidebar(); - } - }); - - // Close sidebar when window is resized to desktop size - window.addEventListener('resize', () => { - if (window.innerWidth > 768) { - closeSidebar(); - } - }); -}); diff --git a/index.html b/index.html deleted file mode 100644 index 0e13dc6..0000000 --- a/index.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - Scale Map Maker - - - - -
- - -
-
-

Tools

- - - - - - - - -
- -
-

Line Instructions

-

- Click to add points.
- Right-click to finish. -

-
- -
-

Node Color

-
- - - - - - - - -
-
- -
-

Node Count

-
-
- -
-

Map View

- -
- -
-

Actions

- - - -
-
- -
-
-
-
-
-
-
- - - - - diff --git a/styles.css b/styles.css deleted file mode 100644 index f37108e..0000000 --- a/styles.css +++ /dev/null @@ -1,244 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: Arial, sans-serif; - overflow: hidden; -} - -.container { - display: flex; - height: 100vh; -} - -.toolbar { - width: 250px; - background: #2c3e50; - color: white; - padding: 20px; - overflow-y: auto; - z-index: 1000; -} - -.tool-section { - margin-bottom: 30px; -} - -.tool-section h3 { - margin-bottom: 10px; - font-size: 14px; - color: #ecf0f1; -} - -.tool-btn, .action-btn { - display: block; - width: 100%; - padding: 10px; - margin-bottom: 5px; - background: #34495e; - color: white; - border: 2px solid transparent; - cursor: pointer; - border-radius: 4px; - font-size: 14px; -} - -.tool-btn:hover, .action-btn:hover { - background: #3d566e; -} - -.tool-btn.active { - background: #3498db; - border-color: #2980b9; -} - -.action-btn { - background: #e74c3c; -} - -.action-btn:hover { - background: #c0392b; -} - -.color-palette { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 8px; -} - -.color-btn { - width: 100%; - height: 40px; - border: 3px solid transparent; - border-radius: 4px; - cursor: pointer; -} - -.color-btn.active { - border-color: white; - box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); -} - -.map-container { - flex: 1; - position: relative; -} - -#map { - width: 100%; - height: 100%; -} - -.ruler-display { - position: absolute; - top: 10px; - right: 10px; - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 10px 15px; - border-radius: 4px; - font-size: 14px; - display: none; - z-index: 1000; - pointer-events: none; -} - -.stats-display { - position: absolute; - top: 10px; - right: 10px; - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 10px 15px; - border-radius: 4px; - font-size: 14px; - z-index: 1000; - pointer-events: none; -} - -.sum-display { - position: absolute; - top: 10px; - left: 10px; - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 10px 15px; - border-radius: 4px; - font-size: 14px; - z-index: 1000; - pointer-events: none; - display: none; -} - -#nodeCount { - font-size: 12px; -} - -.node-count-item { - display: flex; - align-items: center; - margin-bottom: 5px; -} - -.node-count-color { - width: 20px; - height: 20px; - border-radius: 50%; - margin-right: 8px; - border: 2px solid white; -} - -.node-count-text { - color: #ecf0f1; -} - -.leaflet-popup-content { - margin: 8px; - font-size: 13px; -} - -.distance-label { - background: white; - padding: 4px 8px; - border: 2px solid #3498db; - border-radius: 3px; - font-weight: bold; - font-size: 12px; - white-space: nowrap; -} - -.node-marker { - z-index: 1000 !important; -} - -/* Sidebar toggle button */ -.sidebar-toggle { - display: none; - position: fixed; - bottom: 15px; - left: 15px; - z-index: 2000; - background: #2c3e50; - color: white; - border: none; - padding: 12px; - font-size: 18px; - border-radius: 5px; - cursor: pointer; - box-shadow: 0 2px 10px rgba(0,0,0,0.3); -} - -.sidebar-toggle:hover { - background: #34495e; -} - -/* Mobile styles */ -@media (max-width: 768px) { - .sidebar-toggle { - display: block; - } - - .toolbar { - position: fixed; - left: -250px; - top: 0; - height: 100vh; - transition: left 0.3s ease; - z-index: 1500; - box-shadow: 2px 0 10px rgba(0,0,0,0.3); - } - - .toolbar.open { - left: 0; - } - - .map-container { - width: 100%; - } - - /* Hide toggle button when sidebar is open */ - .sidebar-toggle.hidden { - display: none !important; - } - - /* Sum display is fine at top on mobile now that toggle is at bottom */ - - /* Overlay when sidebar is open */ - .sidebar-overlay { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0,0,0,0.5); - z-index: 1400; - } - - .sidebar-overlay.show { - display: block; - } -}