Implement Duplicate Component Feature and Enhance Delete Confirmation in FormBuilder
- Added a duplicate component button in FormBuilderCanvas.vue, allowing users to easily duplicate existing components. - Introduced a delete confirmation modal in both FormBuilderCanvas.vue and FormBuilderFieldSettingsModal.vue to prevent accidental deletions, enhancing user experience. - Updated delete functionality to utilize the confirmation modal, ensuring users are aware of the consequences of their actions. - Refactored related methods to support the new duplication and confirmation features, maintaining clean and organized code. - Enhanced user feedback with toast notifications upon successful deletion and duplication of components.
This commit is contained in:
parent
8d6184fd8b
commit
72a70972fb
@ -64,16 +64,23 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
class="p-1 text-gray-400 hover:text-blue-500 rounded"
|
||||||
|
title="Duplicate component"
|
||||||
|
@click.stop="duplicateComponent(element)"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:document-duplicate" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<!-- <button
|
||||||
class="p-1 text-gray-400 hover:text-gray-600 rounded"
|
class="p-1 text-gray-400 hover:text-gray-600 rounded"
|
||||||
title="Resize component"
|
title="Resize component"
|
||||||
@click.stop="toggleResizeMode(element)"
|
@click.stop="toggleResizeMode(element)"
|
||||||
>
|
>
|
||||||
<Icon name="material-symbols:resize" class="w-4 h-4" />
|
<Icon name="material-symbols:resize" class="w-4 h-4" />
|
||||||
</button>
|
</button> -->
|
||||||
<button
|
<button
|
||||||
class="p-1 text-gray-400 hover:text-red-500 rounded"
|
class="p-1 text-gray-400 hover:text-red-500 rounded"
|
||||||
title="Delete component"
|
title="Delete component"
|
||||||
@click.stop="deleteComponent(element.id)"
|
@click.stop="showDeleteModal(element.id)"
|
||||||
>
|
>
|
||||||
<Icon name="material-symbols:delete-outline" class="w-4 h-4" />
|
<Icon name="material-symbols:delete-outline" class="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -101,6 +108,32 @@
|
|||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Confirmation Modal -->
|
||||||
|
<RsModal v-model="showDeleteConfirmModal" title="Confirm Delete" size="md" position="center">
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<Icon name="material-symbols:warning-outline" class="text-yellow-500 w-8 h-8 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p class="text-gray-600 font-medium mb-1">Delete Component</p>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
Are you sure you want to delete this component? This action cannot be undone and will permanently remove the component from your form.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<RsButton @click="cancelDelete" variant="tertiary">
|
||||||
|
Cancel
|
||||||
|
</RsButton>
|
||||||
|
<RsButton @click="confirmDelete" variant="danger">
|
||||||
|
<Icon name="material-symbols:delete" class="w-4 h-4 mr-1" />
|
||||||
|
Delete
|
||||||
|
</RsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RsModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -115,7 +148,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['select-component', 'move-component', 'delete-component', 'update-component', 'optimize-layout', 'select-nested-component', 'insert-component-at-index']);
|
const emit = defineEmits(['select-component', 'move-component', 'delete-component', 'update-component', 'optimize-layout', 'select-nested-component', 'insert-component-at-index', 'duplicate-component']);
|
||||||
|
|
||||||
const selectedComponentId = ref(null);
|
const selectedComponentId = ref(null);
|
||||||
const resizeMode = ref(false);
|
const resizeMode = ref(false);
|
||||||
@ -123,6 +156,10 @@ const resizing = ref(false);
|
|||||||
const initialWidth = ref(0);
|
const initialWidth = ref(0);
|
||||||
const initialX = ref(0);
|
const initialX = ref(0);
|
||||||
|
|
||||||
|
// Delete modal state
|
||||||
|
const showDeleteConfirmModal = ref(false);
|
||||||
|
const componentToDelete = ref(null);
|
||||||
|
|
||||||
// Watch for changes in formComponents
|
// Watch for changes in formComponents
|
||||||
watch(() => props.formComponents, (newComponents) => {
|
watch(() => props.formComponents, (newComponents) => {
|
||||||
// If the currently selected component is no longer in the list, deselect it
|
// If the currently selected component is no longer in the list, deselect it
|
||||||
@ -155,13 +192,35 @@ const selectComponent = (component) => {
|
|||||||
emit('select-component', componentCopy);
|
emit('select-component', componentCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle component deletion
|
// Handle component duplication
|
||||||
const deleteComponent = (id) => {
|
const duplicateComponent = (component) => {
|
||||||
if (selectedComponentId.value === id) {
|
emit('duplicate-component', component);
|
||||||
selectedComponentId.value = null;
|
};
|
||||||
resizeMode.value = false;
|
|
||||||
|
// Show delete confirmation modal
|
||||||
|
const showDeleteModal = (id) => {
|
||||||
|
componentToDelete.value = id;
|
||||||
|
showDeleteConfirmModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cancel delete operation
|
||||||
|
const cancelDelete = () => {
|
||||||
|
showDeleteConfirmModal.value = false;
|
||||||
|
componentToDelete.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Confirm delete operation
|
||||||
|
const confirmDelete = () => {
|
||||||
|
if (componentToDelete.value) {
|
||||||
|
const id = componentToDelete.value;
|
||||||
|
if (selectedComponentId.value === id) {
|
||||||
|
selectedComponentId.value = null;
|
||||||
|
resizeMode.value = false;
|
||||||
|
}
|
||||||
|
emit('delete-component', id);
|
||||||
}
|
}
|
||||||
emit('delete-component', id);
|
showDeleteConfirmModal.value = false;
|
||||||
|
componentToDelete.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle nested component selection from sections
|
// Handle nested component selection from sections
|
||||||
|
@ -2918,7 +2918,7 @@ if (this.element.querySelector('.file-upload')) {
|
|||||||
|
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<RsButton
|
<RsButton
|
||||||
@click="handleReset"
|
@click="showResetConfirmation"
|
||||||
variant="warning-outline"
|
variant="warning-outline"
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:arrow-path" class="w-4 h-4 mr-1" />
|
<Icon name="heroicons:arrow-path" class="w-4 h-4 mr-1" />
|
||||||
@ -2937,6 +2937,32 @@ if (this.element.querySelector('.file-upload')) {
|
|||||||
</template>
|
</template>
|
||||||
</RsModal>
|
</RsModal>
|
||||||
|
|
||||||
|
<!-- Reset Confirmation Modal -->
|
||||||
|
<RsModal v-model="showResetModal" title="Confirm Reset" size="md" position="center">
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<Icon name="material-symbols:warning-outline" class="text-yellow-500 w-8 h-8 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p class="text-gray-600 font-medium mb-1">Reset to Default Settings</p>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
Are you sure you want to reset this component to its default settings? This action will overwrite all current configuration and cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<RsButton @click="cancelReset" variant="tertiary">
|
||||||
|
Cancel
|
||||||
|
</RsButton>
|
||||||
|
<RsButton @click="confirmReset" variant="warning">
|
||||||
|
<Icon name="heroicons:arrow-path" class="w-4 h-4 mr-1" />
|
||||||
|
Reset to Default
|
||||||
|
</RsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RsModal>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -2970,6 +2996,7 @@ const configModel = ref({
|
|||||||
// Type changing state
|
// Type changing state
|
||||||
const pendingTypeChange = ref(null)
|
const pendingTypeChange = ref(null)
|
||||||
const showTypeChangePreview = ref(false)
|
const showTypeChangePreview = ref(false)
|
||||||
|
const showResetModal = ref(false)
|
||||||
|
|
||||||
// Component info helpers
|
// Component info helpers
|
||||||
const modalTitle = computed(() => {
|
const modalTitle = computed(() => {
|
||||||
@ -3756,6 +3783,19 @@ const handleSave = () => {
|
|||||||
handleClose()
|
handleClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showResetConfirmation = () => {
|
||||||
|
showResetModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelReset = () => {
|
||||||
|
showResetModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmReset = () => {
|
||||||
|
handleReset();
|
||||||
|
showResetModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (props.component) {
|
if (props.component) {
|
||||||
// Reset to default props from the component definition
|
// Reset to default props from the component definition
|
||||||
|
@ -371,6 +371,21 @@ const formatCurrentCode = async () => {
|
|||||||
|
|
||||||
const debouncedFormatCode = useDebounceFn(formatCurrentCode, 300);
|
const debouncedFormatCode = useDebounceFn(formatCurrentCode, 300);
|
||||||
|
|
||||||
|
// Helper functions for format button
|
||||||
|
const getFormatButtonTitle = () => {
|
||||||
|
const langName = props.language === 'json' ? 'JSON' : props.language.toUpperCase();
|
||||||
|
return `Format ${langName} code (Shift+Alt+F)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormatButtonIcon = () => {
|
||||||
|
return 'material-symbols:auto-fix-high';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormatButtonText = () => {
|
||||||
|
const langName = props.language === 'json' ? 'JSON' : props.language.toUpperCase();
|
||||||
|
return `Format ${langName}`;
|
||||||
|
};
|
||||||
|
|
||||||
// Fullscreen functionality
|
// Fullscreen functionality
|
||||||
const isFullscreen = ref(false);
|
const isFullscreen = ref(false);
|
||||||
const fullscreenContainer = ref(null);
|
const fullscreenContainer = ref(null);
|
||||||
|
@ -330,6 +330,7 @@
|
|||||||
@select-nested-component="handleSelectNestedComponent"
|
@select-nested-component="handleSelectNestedComponent"
|
||||||
@move-component="handleMoveComponent"
|
@move-component="handleMoveComponent"
|
||||||
@delete-component="handleDeleteComponent"
|
@delete-component="handleDeleteComponent"
|
||||||
|
@duplicate-component="handleDuplicateComponent"
|
||||||
@update-component="handleUpdateComponent"
|
@update-component="handleUpdateComponent"
|
||||||
@optimize-layout="handleOptimizeLayout"
|
@optimize-layout="handleOptimizeLayout"
|
||||||
@insert-component-at-index="handleInsertComponentAtIndex"
|
@insert-component-at-index="handleInsertComponentAtIndex"
|
||||||
@ -426,16 +427,9 @@
|
|||||||
<Icon name="heroicons:adjustments-horizontal" class="w-4 h-4 mr-1.5" />
|
<Icon name="heroicons:adjustments-horizontal" class="w-4 h-4 mr-1.5" />
|
||||||
Full Settings
|
Full Settings
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="duplicateComponent"
|
@click="showDeleteConfirmation"
|
||||||
class="action-btn secondary"
|
|
||||||
title="Duplicate this component"
|
|
||||||
>
|
|
||||||
<Icon name="heroicons:document-duplicate" class="w-4 h-4 mr-1.5" />
|
|
||||||
Duplicate
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="deleteComponent"
|
|
||||||
class="action-btn danger"
|
class="action-btn danger"
|
||||||
title="Delete this component"
|
title="Delete this component"
|
||||||
>
|
>
|
||||||
@ -1288,6 +1282,84 @@
|
|||||||
@restored="handleFormRestored"
|
@restored="handleFormRestored"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Delete Confirmation Modal for Settings Panel -->
|
||||||
|
<RsModal v-model="showDeleteModal" title="Confirm Delete" size="md" position="center">
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<Icon name="material-symbols:warning-outline" class="text-yellow-500 w-8 h-8 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p class="text-gray-600 font-medium mb-1">Delete Component</p>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
Are you sure you want to delete this component? This action cannot be undone and will permanently remove the component from your form.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<RsButton @click="cancelDelete" variant="tertiary">
|
||||||
|
Cancel
|
||||||
|
</RsButton>
|
||||||
|
<RsButton @click="confirmDelete" variant="danger">
|
||||||
|
<Icon name="material-symbols:delete" class="w-4 h-4 mr-1" />
|
||||||
|
Delete
|
||||||
|
</RsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RsModal>
|
||||||
|
|
||||||
|
<!-- Replace Content Confirmation Modal -->
|
||||||
|
<RsModal v-model="showReplaceContentModal" title="Confirm Replace Content" size="md" position="center">
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<Icon name="material-symbols:warning-outline" class="text-yellow-500 w-8 h-8 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p class="text-gray-600 font-medium mb-1">Replace Current Form Content</p>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
This will replace your current form content. Any unsaved changes will be lost. Are you sure you want to continue?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<RsButton @click="cancelReplaceContent" variant="tertiary">
|
||||||
|
Cancel
|
||||||
|
</RsButton>
|
||||||
|
<RsButton @click="confirmReplaceContent" variant="warning">
|
||||||
|
<Icon name="material-symbols:swap-horiz" class="w-4 h-4 mr-1" />
|
||||||
|
Replace Content
|
||||||
|
</RsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RsModal>
|
||||||
|
|
||||||
|
<!-- Update Form Name Confirmation Modal -->
|
||||||
|
<RsModal v-model="showUpdateFormNameModal" title="Update Form Name" size="md" position="center">
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<Icon name="material-symbols:warning-outline" class="text-yellow-500 w-8 h-8 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p class="text-gray-600 font-medium mb-1">Update Form Name</p>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
Do you want to update the form name to match the template name "{{ pendingFormNameUpdate }}"?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<RsButton @click="cancelUpdateFormName" variant="tertiary">
|
||||||
|
Keep Current Name
|
||||||
|
</RsButton>
|
||||||
|
<RsButton @click="confirmUpdateFormName" variant="primary">
|
||||||
|
<Icon name="material-symbols:edit" class="w-4 h-4 mr-1" />
|
||||||
|
Update Name
|
||||||
|
</RsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RsModal>
|
||||||
|
|
||||||
<!-- Help Modal -->
|
<!-- Help Modal -->
|
||||||
<RsModal v-model="showHelpModal" title="How to Use Form Builder" size="xl" position="center">
|
<RsModal v-model="showHelpModal" title="How to Use Form Builder" size="xl" position="center">
|
||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
@ -1980,11 +2052,11 @@ const handleJsonImport = (event) => {
|
|||||||
} else {
|
} else {
|
||||||
// Confirm before replacing current form
|
// Confirm before replacing current form
|
||||||
if (formStore.formComponents.length > 0) {
|
if (formStore.formComponents.length > 0) {
|
||||||
if (!confirm("This will replace your current form content. Continue?")) {
|
// Store the pending import for later confirmation
|
||||||
// Reset file input
|
pendingJsonImport.value = importedJson;
|
||||||
event.target.value = '';
|
fileInputToReset.value = event.target;
|
||||||
return;
|
showReplaceContentModal.value = true;
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate imported JSON
|
// Validate imported JSON
|
||||||
@ -2400,13 +2472,157 @@ const handleDeleteComponent = (id) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Deleted nested component from container:', deletedComponent);
|
console.log('Deleted nested component from container:', deletedComponent);
|
||||||
|
toast.success('Component deleted successfully');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If not found in sections, delete as a main component
|
// If not found in sections, delete as a main component
|
||||||
formStore.deleteComponent(id);
|
formStore.deleteComponent(id);
|
||||||
|
toast.success('Component deleted successfully');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDuplicateComponent = (component) => {
|
||||||
|
if (!component) return;
|
||||||
|
|
||||||
|
// Create a deep copy of the component props to avoid reference issues
|
||||||
|
const duplicatedProps = JSON.parse(JSON.stringify(component.props));
|
||||||
|
|
||||||
|
// Update name to avoid conflicts
|
||||||
|
if (duplicatedProps.name) {
|
||||||
|
duplicatedProps.name = duplicatedProps.name + '_copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update label to indicate it's a copy
|
||||||
|
if (duplicatedProps.label) {
|
||||||
|
duplicatedProps.label = duplicatedProps.label + ' (Copy)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the component definition for duplication
|
||||||
|
// Note: We don't set an ID here as the store's addComponent method will generate a new unique ID
|
||||||
|
const componentToDuplicate = {
|
||||||
|
type: component.type,
|
||||||
|
name: getComponentTypeName(component.type) + ' (Copy)',
|
||||||
|
category: getDefaultCategory(component.type),
|
||||||
|
icon: getComponentIcon(component.type),
|
||||||
|
defaultProps: duplicatedProps
|
||||||
|
};
|
||||||
|
|
||||||
|
// The addComponent method in the store will automatically assign a new unique ID
|
||||||
|
formStore.addComponent(componentToDuplicate);
|
||||||
|
toast.success('Component duplicated successfully');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete confirmation modal state
|
||||||
|
const showDeleteModal = ref(false);
|
||||||
|
|
||||||
|
// Replace content confirmation modal state
|
||||||
|
const showReplaceContentModal = ref(false);
|
||||||
|
const pendingJsonImport = ref(null);
|
||||||
|
const pendingTemplate = ref(null);
|
||||||
|
const fileInputToReset = ref(null);
|
||||||
|
|
||||||
|
// Update form name confirmation modal state
|
||||||
|
const showUpdateFormNameModal = ref(false);
|
||||||
|
const pendingFormNameUpdate = ref('');
|
||||||
|
|
||||||
|
const showDeleteConfirmation = () => {
|
||||||
|
if (!formStore.selectedComponent) return;
|
||||||
|
showDeleteModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelDelete = () => {
|
||||||
|
showDeleteModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = () => {
|
||||||
|
if (formStore.selectedComponent) {
|
||||||
|
formStore.deleteComponent(formStore.selectedComponent.id);
|
||||||
|
toast.success('Component deleted successfully');
|
||||||
|
}
|
||||||
|
showDeleteModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace content confirmation functions
|
||||||
|
const confirmReplaceContent = () => {
|
||||||
|
try {
|
||||||
|
if (pendingJsonImport.value) {
|
||||||
|
// Handle JSON import
|
||||||
|
if (!pendingJsonImport.value.formName || !Array.isArray(pendingJsonImport.value.components)) {
|
||||||
|
throw new Error('Invalid form JSON structure');
|
||||||
|
}
|
||||||
|
|
||||||
|
formStore.formName = pendingJsonImport.value.formName || 'Imported Form';
|
||||||
|
formStore.formDescription = pendingJsonImport.value.formDescription || '';
|
||||||
|
formStore.formComponents = [];
|
||||||
|
formStore.currentFormId = null;
|
||||||
|
|
||||||
|
pendingJsonImport.value.components.forEach(component => {
|
||||||
|
formStore.addComponent(component);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pendingJsonImport.value.customScript) {
|
||||||
|
formStore.formCustomScript = pendingJsonImport.value.customScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingJsonImport.value.customCSS) {
|
||||||
|
formStore.formCustomCSS = pendingJsonImport.value.customCSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('JSON imported successfully');
|
||||||
|
} else if (pendingTemplate.value) {
|
||||||
|
// Handle template application
|
||||||
|
const template = pendingTemplate.value;
|
||||||
|
|
||||||
|
formStore.formComponents = [];
|
||||||
|
formStore.currentFormId = null;
|
||||||
|
|
||||||
|
if (template.components && Array.isArray(template.components)) {
|
||||||
|
template.components.forEach(component => {
|
||||||
|
formStore.addComponent(component);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`Template "${template.name}" applied successfully`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error replacing content:', error);
|
||||||
|
toast.error('Failed to replace content: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
// Reset state
|
||||||
|
showReplaceContentModal.value = false;
|
||||||
|
pendingJsonImport.value = null;
|
||||||
|
pendingTemplate.value = null;
|
||||||
|
if (fileInputToReset.value) {
|
||||||
|
fileInputToReset.value.value = '';
|
||||||
|
fileInputToReset.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelReplaceContent = () => {
|
||||||
|
showReplaceContentModal.value = false;
|
||||||
|
pendingJsonImport.value = null;
|
||||||
|
pendingTemplate.value = null;
|
||||||
|
if (fileInputToReset.value) {
|
||||||
|
fileInputToReset.value.value = '';
|
||||||
|
fileInputToReset.value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update form name confirmation functions
|
||||||
|
const confirmUpdateFormName = () => {
|
||||||
|
if (pendingFormNameUpdate.value) {
|
||||||
|
formStore.setFormName(pendingFormNameUpdate.value);
|
||||||
|
}
|
||||||
|
showUpdateFormNameModal.value = false;
|
||||||
|
pendingFormNameUpdate.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelUpdateFormName = () => {
|
||||||
|
showUpdateFormNameModal.value = false;
|
||||||
|
pendingFormNameUpdate.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
const handleDragOver = (event) => {
|
const handleDragOver = (event) => {
|
||||||
// Always prevent default to enable drop
|
// Always prevent default to enable drop
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -2812,9 +3028,9 @@ function handleClickOutside(event) {
|
|||||||
const applyFormTemplate = (template) => {
|
const applyFormTemplate = (template) => {
|
||||||
// Confirm if there's already content in the form
|
// Confirm if there's already content in the form
|
||||||
if (formStore.formComponents.length > 0) {
|
if (formStore.formComponents.length > 0) {
|
||||||
if (!confirm("This will replace your current form content. Continue?")) {
|
pendingTemplate.value = template;
|
||||||
return;
|
showReplaceContentModal.value = true;
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2822,8 +3038,11 @@ const applyFormTemplate = (template) => {
|
|||||||
console.log('Template components:', template.components ? template.components.length : 0);
|
console.log('Template components:', template.components ? template.components.length : 0);
|
||||||
|
|
||||||
// Set form name if it's a new form or user allows overwrite
|
// Set form name if it's a new form or user allows overwrite
|
||||||
if (formStore.formName === 'New Form' || confirm("Update the form name to match the template?")) {
|
if (formStore.formName === 'New Form') {
|
||||||
formStore.setFormName(template.name);
|
formStore.setFormName(template.name);
|
||||||
|
} else {
|
||||||
|
pendingFormNameUpdate.value = template.name;
|
||||||
|
showUpdateFormNameModal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the form components and form ID (to ensure this becomes a new form)
|
// Reset the form components and form ID (to ensure this becomes a new form)
|
||||||
@ -3336,44 +3555,9 @@ const openFullSettingsModal = () => {
|
|||||||
showFieldSettings.value = true;
|
showFieldSettings.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Duplicate component
|
|
||||||
const duplicateComponent = () => {
|
|
||||||
if (!formStore.selectedComponent) return;
|
|
||||||
|
|
||||||
const originalComponent = formStore.selectedComponent;
|
|
||||||
const duplicatedProps = { ...originalComponent.props };
|
|
||||||
|
|
||||||
// Update name to avoid conflicts
|
|
||||||
if (duplicatedProps.name) {
|
|
||||||
duplicatedProps.name = duplicatedProps.name + '_copy';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update label to indicate it's a copy
|
|
||||||
if (duplicatedProps.label) {
|
|
||||||
duplicatedProps.label = duplicatedProps.label + ' (Copy)';
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentToDuplicate = {
|
|
||||||
type: originalComponent.type,
|
|
||||||
name: getComponentTypeName(originalComponent.type) + ' (Copy)',
|
|
||||||
category: getDefaultCategory(originalComponent.type),
|
|
||||||
icon: getComponentIcon(originalComponent.type),
|
|
||||||
defaultProps: duplicatedProps
|
|
||||||
};
|
|
||||||
|
|
||||||
formStore.addComponent(componentToDuplicate);
|
|
||||||
toast.success('Component duplicated successfully');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delete component
|
|
||||||
const deleteComponent = () => {
|
|
||||||
if (!formStore.selectedComponent) return;
|
|
||||||
|
|
||||||
if (confirm('Are you sure you want to delete this component?')) {
|
|
||||||
formStore.deleteComponent(formStore.selectedComponent.id);
|
|
||||||
toast.success('Component deleted successfully');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConditionalLogicGenerated = (script) => {
|
const handleConditionalLogicGenerated = (script) => {
|
||||||
// Add the generated script to the form's custom script
|
// Add the generated script to the form's custom script
|
||||||
|
Loading…
x
Reference in New Issue
Block a user