snap-to-grid #1

Closed
dennis-orlando wants to merge 3 commits from snap-to-grid into ours
3 changed files with 145 additions and 33 deletions
Showing only changes of commit 2f4ceba8f1 - Show all commits

View file

@ -2599,6 +2599,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);
@ -2606,6 +2608,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 {
@ -2631,24 +2635,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) {
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
const currentStyle = layer.options || {};
var currentStyle = layer.options || {};
layer.setStyle(Object.assign({}, currentStyle, {
cursor: 'pointer',
opacity: Math.max(currentStyle.opacity || 0, 0.5)
}));
layerCount++;
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');
}
@ -2692,8 +2706,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);
@ -2702,15 +2716,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) {
@ -2736,6 +2758,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;
@ -2749,10 +2821,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({
@ -2770,8 +2847,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');
@ -2784,7 +2861,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');
@ -2795,7 +2872,7 @@ editor.cloneFloor = {
$('#execute-clone-btn').prop('disabled', true).html('<i class="glyphicon glyphicon-refresh"></i> Cloning...');
// Prepare request data
const requestData = {
var requestData = {
source_level_id: currentLevelId,
target_level_id: parseInt(targetLevelId),
items: editor.cloneFloor.selectedItems,
@ -2828,7 +2905,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);
});
}
@ -2840,15 +2917,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('<i class="glyphicon glyphicon-ok"></i> Clone Items');

View file

@ -51,7 +51,13 @@
<button id="execute-clone-btn" class="btn btn-primary btn-sm">
<i class="glyphicon glyphicon-ok"></i> Clone Items
</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
</button>
</div>

View file

@ -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()