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]) => `
-
- `).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
-
-
-
-
-
-
-
-
-
-
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;
- }
-}