From c4cfb4a4f5424aec55f492b9c10f699077d13939 Mon Sep 17 00:00:00 2001 From: Degra02 Date: Sat, 2 Aug 2025 11:00:19 +0200 Subject: [PATCH] functional clone feature --- src/c3nav/editor/static/editor/js/editor.js | 136 +++++++++++++++---- src/c3nav/editor/templates/editor/level.html | 8 +- src/c3nav/editor/utils.py | 34 ++++- 3 files changed, 145 insertions(+), 33 deletions(-) diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index a4448da0..bb70f72e 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -2561,6 +2561,8 @@ editor.cloneFloor = { $('#clone-floor-btn').off('click'); $('#execute-clone-btn').off('click'); $('#cancel-clone-btn').off('click'); + $('#select-all-btn').off('click'); + $('#clear-selection-btn').off('click'); // Bind click event to the button that's already in the template $('#clone-floor-btn').click(editor.cloneFloor.toggleSelectionMode); @@ -2568,6 +2570,8 @@ editor.cloneFloor = { // Bind events to the selector elements that are already in the template $('#execute-clone-btn').click(editor.cloneFloor.executeClone); $('#cancel-clone-btn').click(editor.cloneFloor.cancelSelection); + $('#select-all-btn').click(editor.cloneFloor.selectAllItems); + $('#clear-selection-btn').click(editor.cloneFloor.clearSelection); console.log('Clone floor functionality initialized'); } else { @@ -2593,24 +2597,34 @@ editor.cloneFloor = { $('#clone-floor-selector').show(); editor.cloneFloor.updateSelectedCount(); + // Define supported item types that can be cloned + var supportedTypes = ['area', 'obstacle', 'lineobstacle', 'stair', 'ramp', 'hole', 'column', 'poi', 'altitudemarker', 'space', 'building', 'door']; + // Add click handlers directly to geometry layers if (editor._geometries_layer) { - let layerCount = 0; + var layerCount = 0; + var supportedCount = 0; editor._geometries_layer.eachLayer(function(layer) { if (layer.feature && layer.feature.properties) { - // Add click handler for selection - layer.on('click', editor.cloneFloor.onItemClick); - - // Make layer visually selectable - const currentStyle = layer.options || {}; - layer.setStyle(Object.assign({}, currentStyle, { - cursor: 'pointer', - opacity: Math.max(currentStyle.opacity || 0, 0.5) - })); layerCount++; + var itemType = layer.feature.properties.type; + + // Only make supported types selectable + if (supportedTypes.indexOf(itemType.toLowerCase()) >= 0) { + // Add click handler for selection + layer.on('click', editor.cloneFloor.onItemClick); + + // Make layer visually selectable + var currentStyle = layer.options || {}; + layer.setStyle(Object.assign({}, currentStyle, { + cursor: 'pointer', + opacity: Math.max(currentStyle.opacity || 0, 0.5) + })); + supportedCount++; + } } }); - console.log('Clone floor: Made', layerCount, 'geometries selectable'); + console.log('Clone floor: Made', supportedCount, 'out of', layerCount, 'geometries selectable (supported types only)'); } else { console.log('Clone floor: No geometries layer found'); } @@ -2654,8 +2668,8 @@ editor.cloneFloor = { L.DomEvent.stopPropagation(e); L.DomEvent.preventDefault(e); - const layer = e.target; - const feature = layer.feature; + var layer = e.target; + var feature = layer.feature; console.log('Clone floor: Item clicked', feature); @@ -2664,15 +2678,23 @@ editor.cloneFloor = { return false; } - const itemId = feature.properties.id; - const itemType = feature.properties.type; + var itemId = feature.properties.id; + var itemType = feature.properties.type; console.log('Clone floor: Item ID:', itemId, 'Type:', itemType); console.log('Clone floor: Full feature properties:', JSON.stringify(feature.properties, null, 2)); + // Define supported item types that can be cloned + var supportedTypes = ['area', 'obstacle', 'lineobstacle', 'stair', 'ramp', 'hole', 'column', 'poi', 'altitudemarker', 'space', 'building', 'door']; + + if (supportedTypes.indexOf(itemType.toLowerCase()) === -1) { + console.log('Clone floor: Item type "' + itemType + '" is not supported for cloning. Supported types:', supportedTypes); + return false; + } + // Check if item is already selected - const existingIndex = editor.cloneFloor.selectedItems.findIndex( - item => item.item_id === itemId && item.item_type === itemType + var existingIndex = editor.cloneFloor.selectedItems.findIndex( + function(item) { return item.item_id === itemId && item.item_type === itemType; } ); if (existingIndex >= 0) { @@ -2698,6 +2720,56 @@ editor.cloneFloor = { $('#selected-count').text(editor.cloneFloor.selectedItems.length); }, + selectAllItems: function() { + if (!editor.cloneFloor.isSelectionMode) { + console.log('Clone floor: Select all called but not in selection mode'); + return; + } + + // Clear current selection + editor.cloneFloor.selectedItems = []; + + // Define supported item types that can be cloned + var supportedTypes = ['area', 'obstacle', 'lineobstacle', 'stair', 'ramp', 'hole', 'column', 'poi', 'altitudemarker', 'space', 'building', 'door']; + + if (editor._geometries_layer) { + var selectedCount = 0; + editor._geometries_layer.eachLayer(function(layer) { + if (layer.feature && layer.feature.properties) { + var itemType = layer.feature.properties.type; + var itemId = layer.feature.properties.id; + + // Only select supported types + if (supportedTypes.indexOf(itemType.toLowerCase()) >= 0) { + editor.cloneFloor.selectedItems.push({ + item_id: itemId, + item_type: itemType + }); + selectedCount++; + } + } + }); + console.log('Clone floor: Selected all', selectedCount, 'supported items'); + } + + editor.cloneFloor.updateSelectedCount(); + editor.cloneFloor.updateVisualSelection(); + }, + + clearSelection: function() { + if (!editor.cloneFloor.isSelectionMode) { + console.log('Clone floor: Clear selection called but not in selection mode'); + return; + } + + // Clear current selection + editor.cloneFloor.selectedItems = []; + console.log('Clone floor: Cleared all selected items'); + + editor.cloneFloor.updateSelectedCount(); + editor.cloneFloor.updateVisualSelection(); + }, + updateVisualSelection: function() { if (!editor._geometries_layer) return; @@ -2711,10 +2783,15 @@ editor.cloneFloor = { // Highlight selected items editor._geometries_layer.eachLayer(function(layer) { if (layer.feature && layer.feature.properties) { - const isSelected = editor.cloneFloor.selectedItems.some( - item => item.item_id === layer.feature.properties.id && - item.item_type === layer.feature.properties.type - ); + var isSelected = false; + for (var i = 0; i < editor.cloneFloor.selectedItems.length; i++) { + var item = editor.cloneFloor.selectedItems[i]; + if (item.item_id === layer.feature.properties.id && + item.item_type === layer.feature.properties.type) { + isSelected = true; + break; + } + } if (isSelected) { layer.setStyle({ @@ -2732,8 +2809,8 @@ editor.cloneFloor = { executeClone: function() { - const targetLevelId = $('#target-level-select').val(); - const keepSync = $('#keep-sync-checkbox').is(':checked'); + var targetLevelId = $('#target-level-select').val(); + var keepSync = $('#keep-sync-checkbox').is(':checked'); if (!targetLevelId) { alert('Please select a target level'); @@ -2746,7 +2823,7 @@ editor.cloneFloor = { } // Get current level ID - const currentLevelId = editor._level_control.current_level_id; + var currentLevelId = editor._level_control.current_level_id; if (currentLevelId === parseInt(targetLevelId)) { alert('Source and target levels cannot be the same'); @@ -2757,7 +2834,7 @@ editor.cloneFloor = { $('#execute-clone-btn').prop('disabled', true).html(' Cloning...'); // Prepare request data - const requestData = { + var requestData = { source_level_id: currentLevelId, target_level_id: parseInt(targetLevelId), items: editor.cloneFloor.selectedItems, @@ -2790,7 +2867,7 @@ editor.cloneFloor = { // Log the actual response text for debugging return response.text().then(function(text) { console.error('Clone floor: API error response:', text); - throw new Error(`HTTP ${response.status}: ${response.statusText}`); + throw new Error('HTTP ' + response.status + ': ' + response.statusText); }); } @@ -2802,15 +2879,16 @@ editor.cloneFloor = { console.log('Clone floor: API response keys:', Object.keys(data)); if (data.success) { - alert(`Successfully cloned ${data.cloned_items?.length || 0} items: ${data.message}`); + var clonedCount = data.cloned_items ? data.cloned_items.length : 0; + alert('Successfully cloned ' + clonedCount + ' items: ' + data.message); editor.cloneFloor.cancelSelection(); } else { - alert(`Clone failed: ${data.message}`); + alert('Clone failed: ' + data.message); } }) .catch(function(error) { console.error('Clone floor: Error details:', error); - alert(`Clone failed: ${error.message}`); + alert('Clone failed: ' + error.message); }) .finally(function() { $('#execute-clone-btn').prop('disabled', false).html(' Clone Items'); diff --git a/src/c3nav/editor/templates/editor/level.html b/src/c3nav/editor/templates/editor/level.html index 91e85297..0082939f 100644 --- a/src/c3nav/editor/templates/editor/level.html +++ b/src/c3nav/editor/templates/editor/level.html @@ -51,7 +51,13 @@ - + + diff --git a/src/c3nav/editor/utils.py b/src/c3nav/editor/utils.py index 562f103a..6fdb30c8 100644 --- a/src/c3nav/editor/utils.py +++ b/src/c3nav/editor/utils.py @@ -147,6 +147,7 @@ def clone_level_items(request, source_level_id, target_level_id, items, keep_syn # Handle different item types differently if item_type == 'space': # For spaces, we need level but no space reference + print(f"Processing space item with fields: {[f.name for f in Model._meta.fields]}") for field in Model._meta.fields: if field.name in ['id', 'pk']: continue @@ -154,22 +155,43 @@ def clone_level_items(request, source_level_id, target_level_id, items, keep_syn # Skip auto fields and read-only fields if (hasattr(field, 'auto_created') and field.auto_created) or \ (hasattr(field, 'editable') and not field.editable): + print(f"Skipping field {field.name}: auto_created={getattr(field, 'auto_created', False)}, editable={getattr(field, 'editable', True)}") continue try: field_value = getattr(original_item, field.name) - except (AttributeError, ValueError): + print(f"Field {field.name}: {field_value} (type: {type(field_value)})") + except (AttributeError, ValueError) as e: + print(f"Could not get field {field.name}: {e}") continue # Handle level reference if field.name == 'level': clone_data[field.name] = target_level + print(f"Set level to target_level: {target_level}") else: - # Copy other fields + # Copy other fields - but check for special fields that shouldn't be copied + if field.name in ['slug']: + # Don't copy slug directly as it needs to be unique + # Instead, create a new unique slug based on the original + if field_value: + import re + base_slug = re.sub(r'-\d+$', '', field_value) # Remove trailing numbers + new_slug = f"{base_slug}-clone" + # Make sure the new slug is unique + counter = 1 + test_slug = new_slug + while Model.objects.filter(slug=test_slug).exists(): + test_slug = f"{new_slug}-{counter}" + counter += 1 + clone_data[field.name] = test_slug + print(f"Generated unique slug: {test_slug}") + continue if field_value is not None: clone_data[field.name] = field_value + print(f"Copied field {field.name}: {field_value}") - print(f"Space clone data: {clone_data}") + print(f"Final space clone data: {clone_data}") else: # For space-related items (areas, obstacles, etc.) space_found = False @@ -222,18 +244,24 @@ def clone_level_items(request, source_level_id, target_level_id, items, keep_syn # Create the cloned item try: print(f"Attempting to clone {model_name} with data: {clone_data}") + print(f"Creating {model_name} object...") cloned_item = Model(**clone_data) + print(f"Created object, now saving...") cloned_item.save() print(f"Successfully created cloned item with ID: {cloned_item.pk}") except Exception as create_error: print(f"Error creating {model_name}: {create_error}") + print(f"Error type: {type(create_error)}") print(f"Clone data was: {clone_data}") + # Try a different approach - create empty object and set fields one by one try: + print("Trying field-by-field approach...") cloned_item = Model() for field_name, field_value in clone_data.items(): try: setattr(cloned_item, field_name, field_value) + print(f"Set {field_name} = {field_value}") except Exception as field_error: print(f"Could not set {field_name}={field_value}: {field_error}") cloned_item.save()