Files
markitect-main/markitect/static/js/components/dom-renderer.js
tegwick 28584893d0 feat: rebuild advanced image editor with full drag-n-drop functionality
Completely rebuilt the image editor to match the sophistication of the original
implementation before the modular refactoring. Now includes:

ADVANCED FEATURES RESTORED:
- 🎯 Drag & drop image upload with visual feedback
- 📁 Click-to-select file functionality
- 🖼️ Live image preview with overlay effects
- ✏️ Dedicated alt text editing interface
- ⚠️ Change tracking and unsaved changes indicator
- 🔄 Staging system for managing edits before commit
- 🎨 Professional UI with hover states and transitions

TECHNICAL IMPLEMENTATION:
- FileReader API for local image handling
- Comprehensive drag event management (dragover, dragleave, drop)
- Staging state system tracks original vs modified content
- Visual feedback for drag operations (border color changes)
- Input validation and file type checking
- Reset functionality preserves original state
- Change detection for both image and alt text modifications

USER EXPERIENCE:
- Intuitive drag-and-drop interface
- Real-time preview of changes
- Clear change indicators
- Three-button workflow (Accept/Cancel/Reset)
- Responsive design adapting to content

The image editing experience now provides the full professional-grade
functionality that was present in the original monolithic implementation.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 12:45:17 +01:00

867 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* DOMRenderer Component
*
* Extracted from monolithic editor.js as part of architecture refactoring.
* Handles all DOM interactions and UI rendering for section editing.
*
* Dependencies:
* - FloatingMenu component (to be extracted)
* - debug function (imported from utils)
*/
// Import dependencies (placeholders for now)
function debug(message, category = 'INFO') {
console.log(`DEBUG ${category}: ${message}`);
}
/**
* Simple FloatingMenu implementation (will be extracted to separate component later)
*/
class FloatingMenu {
constructor(sectionId, type, renderer) {
this.sectionId = sectionId;
this.type = type;
this.renderer = renderer;
this.element = null;
this.isVisible = false;
}
show(contentElement, controlsElement) {
if (this.isVisible) this.hide();
const targetElement = this.renderer.findSectionElement(this.sectionId);
if (!targetElement) return null;
// Create floating menu element
this.element = document.createElement('div');
this.element.className = 'ui-edit-floating-menu';
this.element.style.cssText = `
position: fixed;
z-index: 10000;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 16px;
min-width: 300px;
`;
// Position the menu
const rect = targetElement.getBoundingClientRect();
this.element.style.left = `${rect.left}px`;
this.element.style.top = `${rect.bottom + 10}px`;
// Add content
if (contentElement) {
this.element.appendChild(contentElement);
}
if (controlsElement) {
this.element.appendChild(controlsElement);
}
// Add close button
const closeButton = document.createElement('button');
closeButton.textContent = '×';
closeButton.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #666;
`;
closeButton.addEventListener('click', () => this.hide());
this.element.appendChild(closeButton);
document.body.appendChild(this.element);
this.isVisible = true;
return this.element;
}
hide() {
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.element = null;
this.isVisible = false;
// Stop editing state in the section manager
const section = this.renderer.sectionManager.sections.get(this.sectionId);
if (section && section.isEditing()) {
section.stopEditing();
}
// Remove from editing sections
this.renderer.editingSections.delete(this.sectionId);
}
}
/**
* DOMRenderer - Handles DOM interactions and section rendering
*/
class DOMRenderer {
constructor(sectionManager, container) {
this.sectionManager = sectionManager;
this.container = container;
this.editingSections = new Set();
this.currentFloatingMenu = null;
this.eventListenersAttached = false;
// Enhanced Event System - Track event types
this.eventHistory = [];
this.eventStats = {
'section-click': 0,
'section-hover-enter': 0,
'section-hover-leave': 0,
'keyboard-shortcut': 0,
'section-drag-start': 0,
'section-drag-over': 0,
'section-drop': 0,
'section-focus-in': 0,
'section-focus-out': 0,
'section-context-menu': 0
};
// Bind event handlers
this.handleSectionClick = this.handleSectionClick.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.setupEventListeners();
}
setupEventListeners() {
this.sectionManager.on('sections-created', (data) => {
this.renderAllSections(data.sections);
});
this.sectionManager.on('edit-started', (data) => {
debug('EVENT: edit-started event received for: ' + data.sectionId, 'EVENT');
this.showEditor(data.sectionId, data.content);
});
}
/**
* Render all sections to the DOM
*/
renderAllSections(sections) {
debug('21: renderAllSections called with ' + sections.length + ' sections', 'RENDER');
// Clear container
this.container.innerHTML = '';
debug('22: Container cleared', 'RENDER');
const contentArea = this.container.querySelector('#markdown-content') || this.container;
// Render each section
sections.forEach((section, index) => {
debug('23: Creating element for section ' + (index + 1) + ': ' + section.id, 'RENDER');
const element = this.renderSection(section);
if (element) {
contentArea.appendChild(element);
}
});
debug('24: All section elements added to container', 'RENDER');
// Attach event listeners only once
if (!this.eventListenersAttached) {
this.container.addEventListener('click', this.handleSectionClick);
this.eventListenersAttached = true;
debug('25: Enhanced event listeners attached for the first time', 'RENDER');
} else {
debug('25: Event listeners already attached, skipping', 'RENDER');
}
debug('25: Container content length: ' + this.container.innerHTML.length, 'RENDER');
}
/**
* Render a single section to DOM element
*/
renderSection(section) {
const element = document.createElement('div');
element.className = 'ui-edit-section';
element.setAttribute('data-section-id', section.id);
// Add section content
// Render all sections using markdown rendering (images need HTML conversion too)
const content = this.simpleMarkdownRender(section.currentMarkdown);
element.innerHTML = content;
// Add styling
element.style.cssText = `
margin: 16px 0;
padding: 12px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
`;
element.addEventListener('mouseenter', () => {
element.style.backgroundColor = 'rgba(0, 122, 204, 0.05)';
element.style.borderColor = 'rgba(0, 122, 204, 0.2)';
});
element.addEventListener('mouseleave', () => {
if (!section.isEditing()) {
element.style.backgroundColor = 'transparent';
element.style.borderColor = 'transparent';
}
});
debug('SECTION: Section element setup complete for ' + section.id, 'SECTION');
return element;
}
/**
* Simple markdown rendering (placeholder)
*/
simpleMarkdownRender(markdown) {
return markdown
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/!\[(.*?)\]\((.*?)\)/gim, '<img src="$2" alt="$1" style="max-width: 100%; height: auto; display: block; margin: 1rem auto;" />')
.replace(/\[([^\]]+)\]\(([^)]+)\)/gim, '<a href="$2" target="_blank">$1</a>')
.replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
.replace(/\*(.*?)\*/gim, '<em>$1</em>')
.replace(/\n/gim, '<br>');
}
/**
* Find DOM element for a section
*/
findSectionElement(sectionId) {
return this.container.querySelector(`[data-section-id="${sectionId}"]`);
}
/**
* Handle section click events
*/
handleSectionClick(event) {
debug('handleSectionClick: Click detected on target: ' + event.target.tagName + ' ' + (event.target.className || ''), 'CLICK');
// Don't handle clicks on form elements, buttons, or links
if (event.target.closest('textarea, button, input, a')) {
debug('handleSectionClick: Ignoring click on form element', 'CLICK');
return;
}
const sectionElement = event.target.closest('.ui-edit-section');
debug('handleSectionClick: Found section element: ' + (sectionElement ? sectionElement.getAttribute('data-section-id') : 'null'), 'CLICK');
if (!sectionElement) return;
const sectionId = sectionElement.getAttribute('data-section-id');
debug('handleSectionClick: Section ID: ' + sectionId, 'CLICK');
if (!sectionId) return;
// Track the click event
this.trackEvent('section-click', {
sectionId,
event,
timestamp: Date.now()
});
// Check if this section is already being edited
const section = this.sectionManager.sections.get(sectionId);
debug('handleSectionClick: Found section object: ' + (section ? 'YES' : 'NO'), 'CLICK');
if (section && section.isEditing()) {
debug('handleSectionClick: Section already being edited: ' + sectionId, 'CLICK');
return;
}
debug('handleSectionClick: About to start editing for section: ' + sectionId, 'CLICK');
try {
debug('handleSectionClick: Calling sectionManager.startEditing', 'CLICK');
this.sectionManager.startEditing(sectionId);
debug('handleSectionClick: Successfully called startEditing', 'CLICK');
} catch (error) {
debug('handleSectionClick: ERROR in startEditing: ' + error.message, 'ERROR');
console.error('Failed to start editing:', error);
}
}
/**
* Show editor for a section
*/
showEditor(sectionId, content) {
debug('showEditor: called for section: ' + sectionId, 'EDITOR');
const element = this.findSectionElement(sectionId);
debug('showEditor: Found element: ' + (element ? 'YES' : 'NO'), 'EDITOR');
if (!element) return;
debug('showEditor: About to hide current editor', 'EDITOR');
this.hideCurrentEditor();
debug('showEditor: Hidden current editor', 'EDITOR');
const section = this.sectionManager.sections.get(sectionId);
const isImageSection = section && section.isImage();
if (isImageSection) {
this.showImageEditor(sectionId, section);
return;
}
// Create content area for text editing
const editorContent = document.createElement('div');
editorContent.className = 'ui-edit-editor-content';
editorContent.style.cssText = `
display: flex;
flex-direction: column;
gap: 12px;
flex: 1;
min-width: 0;
`;
// Create textarea
const textarea = document.createElement('textarea');
textarea.value = content || section.currentMarkdown;
textarea.style.cssText = `
width: 100%;
min-height: 120px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.5;
resize: vertical;
`;
// Create controls
const controls = document.createElement('div');
controls.style.cssText = `
display: flex;
gap: 8px;
justify-content: flex-end;
`;
const acceptButton = document.createElement('button');
acceptButton.textContent = 'Accept';
acceptButton.style.cssText = `
background: #28a745;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
`;
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.style.cssText = `
background: #dc3545;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
`;
controls.appendChild(acceptButton);
controls.appendChild(cancelButton);
editorContent.appendChild(textarea);
editorContent.appendChild(controls);
// Create floating menu
const floatingMenu = new FloatingMenu(sectionId, 'text', this);
this.currentFloatingMenu = floatingMenu;
this.editingSections.add(sectionId);
floatingMenu.show(editorContent);
// Add event listeners
acceptButton.addEventListener('click', () => {
this.sectionManager.updateContent(sectionId, textarea.value);
this.sectionManager.acceptChanges(sectionId);
floatingMenu.hide();
this.currentFloatingMenu = null; // Clear reference
});
cancelButton.addEventListener('click', () => {
this.sectionManager.cancelChanges(sectionId);
floatingMenu.hide();
this.currentFloatingMenu = null; // Clear reference
});
// Auto-focus textarea
setTimeout(() => textarea.focus(), 100);
}
/**
* Show advanced image editor with drag & drop, file upload, and preview
*/
showImageEditor(sectionId, section) {
debug('showImageEditor: called for image section: ' + sectionId, 'EDITOR');
// Track staging state for this editor
const stagingState = {
originalMarkdown: section.currentMarkdown,
currentAltText: '',
currentImageSrc: '',
stagedImageSrc: null,
stagedAltText: null,
hasChanges: false
};
// Parse markdown to extract image info
const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
if (imageMatch) {
const [, altText, imageSrc] = imageMatch;
stagingState.currentAltText = altText;
stagingState.currentImageSrc = imageSrc;
}
// Create image editor content area
const editorContent = document.createElement('div');
editorContent.className = 'ui-edit-image-content';
editorContent.style.cssText = `
display: flex;
flex-direction: column;
gap: 15px;
flex: 1;
min-width: 0;
`;
// Image preview with drop zone
const imagePreview = document.createElement('div');
imagePreview.className = 'ui-edit-image-preview';
imagePreview.style.cssText = `
width: 100%;
height: 180px;
text-align: center;
background: white;
padding: 12px;
border-radius: 8px;
border: 2px dashed #007bff;
transition: all 0.3s ease;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
box-sizing: border-box;
overflow: hidden;
`;
// Function to update image preview
const updateImagePreview = (imageSrc, altText) => {
imagePreview.innerHTML = '';
if (imageSrc) {
const img = document.createElement('img');
img.src = imageSrc;
img.alt = altText || '';
img.style.cssText = `
max-width: 100%;
max-height: 150px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
`;
imagePreview.appendChild(img);
// Add overlay for drop zone
const overlay = document.createElement('div');
overlay.className = 'drop-overlay';
overlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 123, 255, 0.1);
border-radius: 6px;
display: none;
align-items: center;
justify-content: center;
color: #007bff;
font-weight: bold;
font-size: 16px;
`;
overlay.textContent = '📁 Drop new image here';
imagePreview.appendChild(overlay);
} else {
// Show drop zone placeholder
const placeholder = document.createElement('div');
placeholder.style.cssText = `
text-align: center;
color: #6c757d;
font-size: 14px;
`;
placeholder.innerHTML = `
<div style="font-size: 48px; margin-bottom: 12px;">📁</div>
<div style="margin-bottom: 8px;"><strong>Drop image here or click to select</strong></div>
<div style="font-size: 12px;">Supports JPG, PNG, GIF, WebP</div>
`;
imagePreview.appendChild(placeholder);
}
};
// Initialize preview
updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText);
// File input for image selection
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
// Function to handle image file selection
const handleImageFile = (file) => {
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (event) => {
stagingState.stagedImageSrc = event.target.result;
stagingState.hasChanges = true;
updateImagePreview(stagingState.stagedImageSrc, altTextInput.value);
updateChangeIndicator();
};
reader.readAsDataURL(file);
}
};
// Drag and drop functionality
imagePreview.addEventListener('dragover', (e) => {
e.preventDefault();
imagePreview.style.borderColor = '#28a745';
imagePreview.style.backgroundColor = '#f8fff8';
const overlay = imagePreview.querySelector('.drop-overlay');
if (overlay) overlay.style.display = 'flex';
});
imagePreview.addEventListener('dragleave', (e) => {
e.preventDefault();
imagePreview.style.borderColor = '#007bff';
imagePreview.style.backgroundColor = 'white';
const overlay = imagePreview.querySelector('.drop-overlay');
if (overlay) overlay.style.display = 'none';
});
imagePreview.addEventListener('drop', (e) => {
e.preventDefault();
imagePreview.style.borderColor = '#007bff';
imagePreview.style.backgroundColor = 'white';
const overlay = imagePreview.querySelector('.drop-overlay');
if (overlay) overlay.style.display = 'none';
const files = e.dataTransfer.files;
if (files.length > 0) {
handleImageFile(files[0]);
}
});
// Click to select file
imagePreview.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleImageFile(e.target.files[0]);
}
});
// Alt text editor
const altTextContainer = document.createElement('div');
altTextContainer.className = 'ui-edit-alt-text-container';
altTextContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
`;
const altTextLabel = document.createElement('label');
altTextLabel.textContent = 'Alt Text Description:';
altTextLabel.style.cssText = `
font-size: 13px;
font-weight: 600;
color: #333;
margin: 0;
`;
const altTextInput = document.createElement('input');
altTextInput.type = 'text';
altTextInput.value = stagingState.currentAltText;
altTextInput.style.cssText = `
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
outline: none;
transition: border-color 0.2s ease;
`;
altTextInput.addEventListener('focus', () => {
altTextInput.style.borderColor = '#007bff';
});
altTextInput.addEventListener('blur', () => {
altTextInput.style.borderColor = '#ddd';
});
// Track alt text changes
altTextInput.addEventListener('input', () => {
stagingState.stagedAltText = altTextInput.value;
stagingState.hasChanges = altTextInput.value !== stagingState.currentAltText || stagingState.stagedImageSrc !== null;
updateChangeIndicator();
});
altTextContainer.appendChild(altTextLabel);
altTextContainer.appendChild(altTextInput);
// Change indicator
const changeIndicator = document.createElement('div');
changeIndicator.className = 'change-indicator';
changeIndicator.style.cssText = `
padding: 8px 12px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
color: #856404;
font-size: 12px;
text-align: center;
display: none;
font-weight: 500;
`;
changeIndicator.textContent = '⚠️ You have unsaved changes';
const updateChangeIndicator = () => {
if (stagingState.hasChanges) {
changeIndicator.style.display = 'block';
} else {
changeIndicator.style.display = 'none';
}
};
// Assemble content
editorContent.appendChild(imagePreview);
editorContent.appendChild(altTextContainer);
editorContent.appendChild(changeIndicator);
editorContent.appendChild(fileInput);
// Create controls
const controls = document.createElement('div');
controls.className = 'ui-edit-controls';
controls.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
`;
const acceptBtn = document.createElement('button');
acceptBtn.textContent = '✓ Accept';
acceptBtn.style.cssText = `
padding: 8px 12px;
font-size: 12px;
border-radius: 6px;
border: none;
color: white;
cursor: pointer;
font-weight: 600;
transition: all 0.2s ease;
width: 100%;
text-align: center;
background: #28a745;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '✗ Cancel';
cancelBtn.style.cssText = `
padding: 8px 12px;
font-size: 12px;
border-radius: 6px;
border: none;
color: white;
cursor: pointer;
font-weight: 600;
transition: all 0.2s ease;
width: 100%;
text-align: center;
background: #dc3545;
`;
const resetBtn = document.createElement('button');
resetBtn.textContent = '↺ Reset';
resetBtn.style.cssText = `
padding: 8px 12px;
font-size: 12px;
border-radius: 6px;
border: none;
color: white;
cursor: pointer;
font-weight: 600;
transition: all 0.2s ease;
width: 100%;
text-align: center;
background: #fd7e14;
`;
controls.appendChild(acceptBtn);
controls.appendChild(cancelBtn);
controls.appendChild(resetBtn);
// Event handlers
acceptBtn.addEventListener('click', () => {
// Apply staged changes only when accept is clicked
if (stagingState.hasChanges) {
let newMarkdown = stagingState.originalMarkdown;
// Apply image source change if staged
if (stagingState.stagedImageSrc !== null) {
const currentImageMatch = newMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
if (currentImageMatch) {
newMarkdown = newMarkdown.replace(
/!\[(.*?)\]\((.*?)\)/,
`![${currentImageMatch[1]}](${stagingState.stagedImageSrc})`
);
}
}
// Apply alt text change if staged
if (stagingState.stagedAltText !== null) {
newMarkdown = newMarkdown.replace(
/!\[(.*?)\]/,
`![${stagingState.stagedAltText}]`
);
}
// Update section with final changes
this.sectionManager.updateContent(sectionId, newMarkdown);
}
// Accept changes and hide editor
this.sectionManager.acceptChanges(sectionId);
this.currentFloatingMenu.hide();
this.currentFloatingMenu = null;
});
cancelBtn.addEventListener('click', () => {
// Discard all staged changes and hide editor
this.sectionManager.cancelChanges(sectionId);
this.currentFloatingMenu.hide();
this.currentFloatingMenu = null;
});
resetBtn.addEventListener('click', () => {
// Reset to original content
const originalImageMatch = stagingState.originalMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
if (originalImageMatch) {
const [, originalAltText, originalImageSrc] = originalImageMatch;
stagingState.currentAltText = originalAltText;
stagingState.currentImageSrc = originalImageSrc;
}
// Clear any staged changes
stagingState.stagedImageSrc = null;
stagingState.stagedAltText = null;
stagingState.hasChanges = false;
// Reset alt text input to original
altTextInput.value = stagingState.currentAltText;
// Reset preview to original
updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText);
updateChangeIndicator();
});
// Create floating menu
const floatingMenu = new FloatingMenu(sectionId, 'image', this);
this.currentFloatingMenu = floatingMenu;
this.editingSections.add(sectionId);
floatingMenu.show(editorContent, controls);
}
/**
* Hide current editor
*/
hideCurrentEditor() {
debug('EDITOR: hideCurrentEditor called', 'EDITOR');
if (this.currentFloatingMenu) {
this.currentFloatingMenu.hide();
this.currentFloatingMenu = null;
}
debug('EDITOR: hideCurrentEditor completed', 'EDITOR');
}
/**
* Track event for analytics
*/
trackEvent(eventType, data) {
const eventRecord = {
type: eventType,
data: data,
timestamp: new Date().toISOString()
};
this.eventHistory.push(eventRecord);
if (this.eventStats.hasOwnProperty(eventType)) {
this.eventStats[eventType]++;
}
// Keep only last 100 events
if (this.eventHistory.length > 100) {
this.eventHistory = this.eventHistory.slice(-100);
}
}
/**
* Get event statistics
*/
getEventStats() {
const totalEvents = Object.values(this.eventStats).reduce((sum, count) => sum + count, 0);
return {
stats: { ...this.eventStats },
totalEvents,
recentEvents: this.eventHistory.slice(-10)
};
}
/**
* Handle keyboard shortcuts
*/
handleKeydown(event) {
// Basic keyboard shortcut handling
if (event.ctrlKey || event.metaKey) {
if (event.key === 'Enter') {
// Accept changes
const activeSection = Array.from(this.editingSections)[0];
if (activeSection) {
this.trackEvent('keyboard-shortcut', { shortcut: 'ctrl+enter', action: 'accept' });
}
} else if (event.key === 'Escape') {
// Cancel changes
const activeSection = Array.from(this.editingSections)[0];
if (activeSection) {
this.trackEvent('keyboard-shortcut', { shortcut: 'escape', action: 'cancel' });
this.hideCurrentEditor();
}
}
}
}
}
// Export for use in tests and other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = { DOMRenderer, FloatingMenu };
}
// Export for browser use
if (typeof window !== 'undefined') {
window.DOMRenderer = DOMRenderer;
window.FloatingMenu = FloatingMenu;
}