snap-to-grid #1
3 changed files with 145 additions and 33 deletions
|
@ -2599,6 +2599,8 @@ editor.cloneFloor = {
|
||||||
$('#clone-floor-btn').off('click');
|
$('#clone-floor-btn').off('click');
|
||||||
$('#execute-clone-btn').off('click');
|
$('#execute-clone-btn').off('click');
|
||||||
$('#cancel-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
|
// Bind click event to the button that's already in the template
|
||||||
$('#clone-floor-btn').click(editor.cloneFloor.toggleSelectionMode);
|
$('#clone-floor-btn').click(editor.cloneFloor.toggleSelectionMode);
|
||||||
|
@ -2606,6 +2608,8 @@ editor.cloneFloor = {
|
||||||
// Bind events to the selector elements that are already in the template
|
// Bind events to the selector elements that are already in the template
|
||||||
$('#execute-clone-btn').click(editor.cloneFloor.executeClone);
|
$('#execute-clone-btn').click(editor.cloneFloor.executeClone);
|
||||||
$('#cancel-clone-btn').click(editor.cloneFloor.cancelSelection);
|
$('#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');
|
console.log('Clone floor functionality initialized');
|
||||||
} else {
|
} else {
|
||||||
|
@ -2631,24 +2635,34 @@ editor.cloneFloor = {
|
||||||
$('#clone-floor-selector').show();
|
$('#clone-floor-selector').show();
|
||||||
editor.cloneFloor.updateSelectedCount();
|
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
|
// Add click handlers directly to geometry layers
|
||||||
if (editor._geometries_layer) {
|
if (editor._geometries_layer) {
|
||||||
let layerCount = 0;
|
var layerCount = 0;
|
||||||
|
var supportedCount = 0;
|
||||||
editor._geometries_layer.eachLayer(function(layer) {
|
editor._geometries_layer.eachLayer(function(layer) {
|
||||||
if (layer.feature && layer.feature.properties) {
|
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++;
|
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 {
|
} else {
|
||||||
console.log('Clone floor: No geometries layer found');
|
console.log('Clone floor: No geometries layer found');
|
||||||
}
|
}
|
||||||
|
@ -2692,8 +2706,8 @@ editor.cloneFloor = {
|
||||||
L.DomEvent.stopPropagation(e);
|
L.DomEvent.stopPropagation(e);
|
||||||
L.DomEvent.preventDefault(e);
|
L.DomEvent.preventDefault(e);
|
||||||
|
|
||||||
const layer = e.target;
|
var layer = e.target;
|
||||||
const feature = layer.feature;
|
var feature = layer.feature;
|
||||||
|
|
||||||
console.log('Clone floor: Item clicked', feature);
|
console.log('Clone floor: Item clicked', feature);
|
||||||
|
|
||||||
|
@ -2702,15 +2716,23 @@ editor.cloneFloor = {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemId = feature.properties.id;
|
var itemId = feature.properties.id;
|
||||||
const itemType = feature.properties.type;
|
var itemType = feature.properties.type;
|
||||||
|
|
||||||
console.log('Clone floor: Item ID:', itemId, 'Type:', itemType);
|
console.log('Clone floor: Item ID:', itemId, 'Type:', itemType);
|
||||||
console.log('Clone floor: Full feature properties:', JSON.stringify(feature.properties, null, 2));
|
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
|
// Check if item is already selected
|
||||||
const existingIndex = editor.cloneFloor.selectedItems.findIndex(
|
var existingIndex = editor.cloneFloor.selectedItems.findIndex(
|
||||||
item => item.item_id === itemId && item.item_type === itemType
|
function(item) { return item.item_id === itemId && item.item_type === itemType; }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
|
@ -2736,6 +2758,56 @@ editor.cloneFloor = {
|
||||||
$('#selected-count').text(editor.cloneFloor.selectedItems.length);
|
$('#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() {
|
updateVisualSelection: function() {
|
||||||
if (!editor._geometries_layer) return;
|
if (!editor._geometries_layer) return;
|
||||||
|
|
||||||
|
@ -2749,10 +2821,15 @@ editor.cloneFloor = {
|
||||||
// Highlight selected items
|
// Highlight selected items
|
||||||
editor._geometries_layer.eachLayer(function(layer) {
|
editor._geometries_layer.eachLayer(function(layer) {
|
||||||
if (layer.feature && layer.feature.properties) {
|
if (layer.feature && layer.feature.properties) {
|
||||||
const isSelected = editor.cloneFloor.selectedItems.some(
|
var isSelected = false;
|
||||||
item => item.item_id === layer.feature.properties.id &&
|
for (var i = 0; i < editor.cloneFloor.selectedItems.length; i++) {
|
||||||
item.item_type === layer.feature.properties.type
|
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) {
|
if (isSelected) {
|
||||||
layer.setStyle({
|
layer.setStyle({
|
||||||
|
@ -2770,8 +2847,8 @@ editor.cloneFloor = {
|
||||||
|
|
||||||
|
|
||||||
executeClone: function() {
|
executeClone: function() {
|
||||||
const targetLevelId = $('#target-level-select').val();
|
var targetLevelId = $('#target-level-select').val();
|
||||||
const keepSync = $('#keep-sync-checkbox').is(':checked');
|
var keepSync = $('#keep-sync-checkbox').is(':checked');
|
||||||
|
|
||||||
if (!targetLevelId) {
|
if (!targetLevelId) {
|
||||||
alert('Please select a target level');
|
alert('Please select a target level');
|
||||||
|
@ -2784,7 +2861,7 @@ editor.cloneFloor = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current level ID
|
// Get current level ID
|
||||||
const currentLevelId = editor._level_control.current_level_id;
|
var currentLevelId = editor._level_control.current_level_id;
|
||||||
|
|
||||||
if (currentLevelId === parseInt(targetLevelId)) {
|
if (currentLevelId === parseInt(targetLevelId)) {
|
||||||
alert('Source and target levels cannot be the same');
|
alert('Source and target levels cannot be the same');
|
||||||
|
@ -2795,7 +2872,7 @@ editor.cloneFloor = {
|
||||||
$('#execute-clone-btn').prop('disabled', true).html('<i class="glyphicon glyphicon-refresh"></i> Cloning...');
|
$('#execute-clone-btn').prop('disabled', true).html('<i class="glyphicon glyphicon-refresh"></i> Cloning...');
|
||||||
|
|
||||||
// Prepare request data
|
// Prepare request data
|
||||||
const requestData = {
|
var requestData = {
|
||||||
source_level_id: currentLevelId,
|
source_level_id: currentLevelId,
|
||||||
target_level_id: parseInt(targetLevelId),
|
target_level_id: parseInt(targetLevelId),
|
||||||
items: editor.cloneFloor.selectedItems,
|
items: editor.cloneFloor.selectedItems,
|
||||||
|
@ -2828,7 +2905,7 @@ editor.cloneFloor = {
|
||||||
// Log the actual response text for debugging
|
// Log the actual response text for debugging
|
||||||
return response.text().then(function(text) {
|
return response.text().then(function(text) {
|
||||||
console.error('Clone floor: API error response:', 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2840,15 +2917,16 @@ editor.cloneFloor = {
|
||||||
console.log('Clone floor: API response keys:', Object.keys(data));
|
console.log('Clone floor: API response keys:', Object.keys(data));
|
||||||
|
|
||||||
if (data.success) {
|
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();
|
editor.cloneFloor.cancelSelection();
|
||||||
} else {
|
} else {
|
||||||
alert(`Clone failed: ${data.message}`);
|
alert('Clone failed: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
console.error('Clone floor: Error details:', error);
|
console.error('Clone floor: Error details:', error);
|
||||||
alert(`Clone failed: ${error.message}`);
|
alert('Clone failed: ' + error.message);
|
||||||
})
|
})
|
||||||
.finally(function() {
|
.finally(function() {
|
||||||
$('#execute-clone-btn').prop('disabled', false).html('<i class="glyphicon glyphicon-ok"></i> Clone Items');
|
$('#execute-clone-btn').prop('disabled', false).html('<i class="glyphicon glyphicon-ok"></i> Clone Items');
|
||||||
|
|
|
@ -51,7 +51,13 @@
|
||||||
<button id="execute-clone-btn" class="btn btn-primary btn-sm">
|
<button id="execute-clone-btn" class="btn btn-primary btn-sm">
|
||||||
<i class="glyphicon glyphicon-ok"></i> Clone Items
|
<i class="glyphicon glyphicon-ok"></i> Clone Items
|
||||||
</button>
|
</button>
|
||||||
<button id="cancel-clone-btn" class="btn btn-default btn-sm">
|
<button id="select-all-btn" class="btn btn-success btn-sm" style="margin-left: 5px;">
|
||||||
|
<i class="glyphicon glyphicon-check"></i> Select All
|
||||||
|
</button>
|
||||||
|
<button id="clear-selection-btn" class="btn btn-warning btn-sm" style="margin-left: 5px;">
|
||||||
|
<i class="glyphicon glyphicon-unchecked"></i> Clear Selection
|
||||||
|
</button>
|
||||||
|
<button id="cancel-clone-btn" class="btn btn-default btn-sm" style="margin-left: 5px;">
|
||||||
<i class="glyphicon glyphicon-remove"></i> Cancel
|
<i class="glyphicon glyphicon-remove"></i> Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -147,6 +147,7 @@ def clone_level_items(request, source_level_id, target_level_id, items, keep_syn
|
||||||
# Handle different item types differently
|
# Handle different item types differently
|
||||||
if item_type == 'space':
|
if item_type == 'space':
|
||||||
# For spaces, we need level but no space reference
|
# 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:
|
for field in Model._meta.fields:
|
||||||
if field.name in ['id', 'pk']:
|
if field.name in ['id', 'pk']:
|
||||||
continue
|
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
|
# Skip auto fields and read-only fields
|
||||||
if (hasattr(field, 'auto_created') and field.auto_created) or \
|
if (hasattr(field, 'auto_created') and field.auto_created) or \
|
||||||
(hasattr(field, 'editable') and not field.editable):
|
(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
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
field_value = getattr(original_item, field.name)
|
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
|
continue
|
||||||
|
|
||||||
# Handle level reference
|
# Handle level reference
|
||||||
if field.name == 'level':
|
if field.name == 'level':
|
||||||
clone_data[field.name] = target_level
|
clone_data[field.name] = target_level
|
||||||
|
print(f"Set level to target_level: {target_level}")
|
||||||
else:
|
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:
|
if field_value is not None:
|
||||||
clone_data[field.name] = field_value
|
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:
|
else:
|
||||||
# For space-related items (areas, obstacles, etc.)
|
# For space-related items (areas, obstacles, etc.)
|
||||||
space_found = False
|
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
|
# Create the cloned item
|
||||||
try:
|
try:
|
||||||
print(f"Attempting to clone {model_name} with data: {clone_data}")
|
print(f"Attempting to clone {model_name} with data: {clone_data}")
|
||||||
|
print(f"Creating {model_name} object...")
|
||||||
cloned_item = Model(**clone_data)
|
cloned_item = Model(**clone_data)
|
||||||
|
print(f"Created object, now saving...")
|
||||||
cloned_item.save()
|
cloned_item.save()
|
||||||
print(f"Successfully created cloned item with ID: {cloned_item.pk}")
|
print(f"Successfully created cloned item with ID: {cloned_item.pk}")
|
||||||
except Exception as create_error:
|
except Exception as create_error:
|
||||||
print(f"Error creating {model_name}: {create_error}")
|
print(f"Error creating {model_name}: {create_error}")
|
||||||
|
print(f"Error type: {type(create_error)}")
|
||||||
print(f"Clone data was: {clone_data}")
|
print(f"Clone data was: {clone_data}")
|
||||||
|
|
||||||
# Try a different approach - create empty object and set fields one by one
|
# Try a different approach - create empty object and set fields one by one
|
||||||
try:
|
try:
|
||||||
|
print("Trying field-by-field approach...")
|
||||||
cloned_item = Model()
|
cloned_item = Model()
|
||||||
for field_name, field_value in clone_data.items():
|
for field_name, field_value in clone_data.items():
|
||||||
try:
|
try:
|
||||||
setattr(cloned_item, field_name, field_value)
|
setattr(cloned_item, field_name, field_value)
|
||||||
|
print(f"Set {field_name} = {field_value}")
|
||||||
except Exception as field_error:
|
except Exception as field_error:
|
||||||
print(f"Could not set {field_name}={field_value}: {field_error}")
|
print(f"Could not set {field_name}={field_value}: {field_error}")
|
||||||
cloned_item.save()
|
cloned_item.save()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue