feat: comprehensive control panel UI improvements

- Fix version information display with actual Markitect version
- Implement auto-resize functionality with double-click on resize dot
- Add viewport repositioning to keep panels visible during auto-resize
- Reduce title bar height by 25% for more compact appearance
- Remove duplicate content titles below titlebars across all panels
- Optimize scrollbar positioning to right border with proper spacing
- Reposition resize dot to optimal corner location (bottom: 0px, right: -4px)
- Set default panel height to 1/3 of window height
- Fix Debug panel title formatting consistency
- Remove duplicate initialization warnings
- Clean up panel layout with proper margin management (10px bottom margin)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 22:51:25 +01:00
parent f788ccdfd3
commit 4e3f112987
9 changed files with 236 additions and 77 deletions

View File

@@ -118,10 +118,10 @@ class ContentsControl extends ControlBase {
if (displayHeadings.length === 0) {
return `
<div style="padding: 1rem; text-align: center; color: #666;">
<div style="text-align: center; color: #666; padding: 2rem 0;">
<p>No headings found in document</p>
<button onclick="this.closest('.contents-control').contentsControl.refreshContents()"
style="padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;">
style="padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; margin-top: 0.5rem;">
🔄 Refresh
</button>
</div>
@@ -129,10 +129,10 @@ class ContentsControl extends ControlBase {
}
const searchHTML = `
<div style="padding: 0.5rem; border-bottom: 1px solid #eee;">
<div style="margin-bottom: 0.5rem; border-bottom: 1px solid #eee; padding-bottom: 0.5rem;">
<input type="text"
placeholder="Search headings..."
style="width: 100%; padding: 0.25rem; border: 1px solid #ddd; border-radius: 3px; font-size: 0.8rem;"
style="width: 100%; padding: 0.25rem; border: 1px solid #ddd; border-radius: 3px; font-size: 0.8rem; box-sizing: border-box;"
onkeyup="this.closest('.contents-control').contentsControl.handleSearch(this.value)">
</div>
`;
@@ -157,15 +157,15 @@ class ContentsControl extends ControlBase {
}).join('');
return `
<div style="padding: 0;">
<div>
${searchHTML}
<div style="max-height: 300px; overflow-y: auto; padding: 0.5rem;">
<div style="margin-bottom: 0.5rem;">
<div style="margin-bottom: 0.5rem; font-size: 0.7rem; color: #666; text-align: center;">
Found ${displayHeadings.length} heading${displayHeadings.length !== 1 ? 's' : ''}
</div>
${contentsHTML}
</div>
<div style="padding: 0.5rem; border-top: 1px solid #eee; text-align: center;">
<div style="border-top: 1px solid #eee; padding-top: 0.5rem; text-align: center;">
<button onclick="this.closest('.contents-control').contentsControl.refreshContents()"
style="padding: 0.3rem 0.6rem; font-size: 0.7rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;">
🔄 Refresh Contents

View File

@@ -59,7 +59,10 @@ class ControlBase {
this.isDragging = false;
this.isResizing = false;
this.position = { x: 0, y: 0 };
this.size = { width: 300, height: 200 };
this.size = {
width: 300,
height: Math.floor(window.innerHeight / 3)
};
this.originalPosition = null; // Store original position for collapse
// Event handlers storage
@@ -258,6 +261,9 @@ class ControlBase {
panel.style.display = 'block';
toggleBtn.style.display = 'none';
// Calculate default height as 1/3 of window height
const defaultHeight = Math.floor(window.innerHeight / 3);
// Style expanded panel
panel.style.cssText = `
position: relative;
@@ -272,7 +278,7 @@ class ControlBase {
min-height: 200px;
max-height: calc(100vh - 40px);
width: auto;
height: auto;
height: ${defaultHeight}px;
overflow: hidden;
`;
@@ -283,13 +289,13 @@ class ControlBase {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
padding: 4px 12px;
background: rgba(0,0,0,0.05);
border-bottom: 1px solid #dee2e6;
cursor: move;
user-select: none;
flex-shrink: 0;
min-height: 40px;
min-height: 24px;
border-radius: 7px 7px 0 0;
margin: -1px -1px 0 -1px;
`;
@@ -365,7 +371,7 @@ class ControlBase {
// Reset internal size tracking
this.size.width = 300;
this.size.height = 200;
this.size.height = Math.floor(window.innerHeight / 3);
this.storedWidth = null;
// Remove resize handle
@@ -534,8 +540,8 @@ class ControlBase {
resizeHandle.innerHTML = '●'; // Dot resize indicator
resizeHandle.style.cssText = `
position: absolute;
bottom: 4px;
right: 20px;
bottom: 0px;
right: -4px;
width: 12px;
height: 12px;
cursor: se-resize;
@@ -554,6 +560,7 @@ class ControlBase {
// Set up resize handlers
this.addEventListener(resizeHandle, 'mousedown', (e) => this.startResize(e));
this.addEventListener(resizeHandle, 'dblclick', (e) => this.autoResizeToContent(e));
}
}
@@ -647,6 +654,89 @@ class ControlBase {
}
}
/**
* Auto-resize panel to fit content size with viewport repositioning
*/
autoResizeToContent(event) {
return this.safeOperation(() => {
event.preventDefault();
event.stopPropagation();
if (!this.isExpanded) return;
const panel = this.element?.querySelector('.control-panel-expanded');
const contentBody = this.element?.querySelector('.control-content-body');
if (!panel || !contentBody) return;
// Get current panel position
const rect = panel.getBoundingClientRect();
const currentLeft = rect.left;
const currentTop = rect.top;
// Measure content size by temporarily allowing natural sizing
const originalOverflow = contentBody.style.overflow;
const originalMaxHeight = panel.style.maxHeight;
const originalHeight = panel.style.height;
const originalWidth = panel.style.width;
// Temporarily remove constraints to measure natural size
contentBody.style.overflow = 'visible';
panel.style.maxHeight = 'none';
panel.style.height = 'auto';
panel.style.width = 'auto';
// Force reflow and measure
panel.offsetHeight; // Force reflow
const contentRect = contentBody.getBoundingClientRect();
const headerHeight = this.element.querySelector('.control-header')?.offsetHeight || 24;
// Calculate ideal size with padding and margins
const idealWidth = Math.max(300, Math.min(window.innerWidth - 40, contentRect.width + 40));
const idealHeight = Math.max(200, Math.min(window.innerHeight - 40, contentRect.height + headerHeight + 40));
// Restore original constraints
contentBody.style.overflow = originalOverflow;
panel.style.maxHeight = originalMaxHeight;
// Calculate new position to keep panel in viewport
let newLeft = currentLeft;
let newTop = currentTop;
// Adjust position if panel would go outside viewport
if (currentLeft + idealWidth > window.innerWidth) {
newLeft = window.innerWidth - idealWidth - 20;
}
if (newLeft < 20) {
newLeft = 20;
}
if (currentTop + idealHeight > window.innerHeight) {
newTop = window.innerHeight - idealHeight - 20;
}
if (newTop < 20) {
newTop = 20;
}
// Apply new size and position
panel.style.width = `${idealWidth}px`;
panel.style.height = `${idealHeight}px`;
// Update position if it changed
if (newLeft !== currentLeft || newTop !== currentTop) {
this.element.style.left = `${newLeft}px`;
this.element.style.top = `${newTop}px`;
this.position.x = newLeft;
this.position.y = newTop;
}
// Update internal size tracking
this.size.width = idealWidth;
this.size.height = idealHeight;
}, null, 'autoResizeToContent');
}
/**
* Position the control based on compass position (used by show method)
*/
@@ -671,20 +761,11 @@ class ControlBase {
// Apply consistent container styling
content.innerHTML = `
<div class="control-content-title" style="
margin: 0 0 1rem 0;
font-weight: 600;
font-size: 1.1em;
color: #333;
padding: 1rem 1rem 0 1rem;
flex-shrink: 0;
">${this.config.title}</div>
<div class="control-content-container" style="
flex: 1;
overflow-y: auto;
margin: 0 1rem;
padding: 0 0 1rem 0;
margin: 0 0 10px 1rem;
padding: 0.75rem 1rem 1rem 0;
font-size: 0.8rem;
box-sizing: border-box;
min-height: 0;
@@ -692,7 +773,7 @@ class ControlBase {
">
<div class="control-content-body" style="
padding: 0;
margin-bottom: 25px;
margin-bottom: 0;
">
${innerContent}
</div>

View File

@@ -167,10 +167,13 @@ class DebugControl extends ControlBase {
'Not available'
};
// Get Markitect version from config or default
const markitectVersion = window.markitectConfig?.version || 'Unknown';
return `
<div class="system-info" style="margin-bottom: 1rem; padding: 0.5rem; background: #f8f9fa; border-radius: 3px; font-size: 0.7rem;">
<strong>System Information:</strong><br>
<div style="margin-top: 0.3rem; line-height: 1.3;">
<div style="line-height: 1.3;">
<div><strong>Markitect:</strong> ${markitectVersion}</div>
<div><strong>Viewport:</strong> ${systemInfo.viewport}</div>
<div><strong>Screen:</strong> ${systemInfo.screen}</div>
<div><strong>Memory:</strong> ${systemInfo.memory}</div>

View File

@@ -1324,7 +1324,14 @@ MISSING: {len(missing_components)} components
config['version'] = f"{version_info['repo_name']} v{version_info['version']}{version_info['git_info']}"
config['repoName'] = version_info['repo_name']
else:
config['version'] = 'Markitect v0.8.1'
# Get version from CLI command as fallback
import subprocess
try:
result = subprocess.run(['markitect', '--version'], capture_output=True, text=True, timeout=5)
actual_version = result.stdout.strip() if result.returncode == 0 else '0.8.1.dev44+gf788ccdfd.d20251114'
except:
actual_version = '0.8.1.dev44+gf788ccdfd.d20251114'
config['version'] = f'Markitect v{actual_version}'
config['repoName'] = 'Markitect'
# Add insert mode specific config

View File

@@ -53,13 +53,7 @@
font-family: monospace;
}
.debug-control .debug-header {
background: #343a40;
color: #fff;
padding: 0.5rem;
margin: -0.75rem -0.75rem 0.5rem -0.75rem;
border-radius: 5px 5px 0 0;
}
/* Removed debug-header styles - using base class title formatting */
.debug-control .debug-logs {
max-height: 200px;

View File

@@ -118,10 +118,10 @@ class ContentsControl extends ControlBase {
if (displayHeadings.length === 0) {
return `
<div style="padding: 1rem; text-align: center; color: #666;">
<div style="text-align: center; color: #666; padding: 2rem 0;">
<p>No headings found in document</p>
<button onclick="this.closest('.contents-control').contentsControl.refreshContents()"
style="padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;">
style="padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; margin-top: 0.5rem;">
🔄 Refresh
</button>
</div>
@@ -129,10 +129,10 @@ class ContentsControl extends ControlBase {
}
const searchHTML = `
<div style="padding: 0.5rem; border-bottom: 1px solid #eee;">
<div style="margin-bottom: 0.5rem; border-bottom: 1px solid #eee; padding-bottom: 0.5rem;">
<input type="text"
placeholder="Search headings..."
style="width: 100%; padding: 0.25rem; border: 1px solid #ddd; border-radius: 3px; font-size: 0.8rem;"
style="width: 100%; padding: 0.25rem; border: 1px solid #ddd; border-radius: 3px; font-size: 0.8rem; box-sizing: border-box;"
onkeyup="this.closest('.contents-control').contentsControl.handleSearch(this.value)">
</div>
`;
@@ -157,15 +157,15 @@ class ContentsControl extends ControlBase {
}).join('');
return `
<div style="padding: 0;">
<div>
${searchHTML}
<div style="max-height: 300px; overflow-y: auto; padding: 0.5rem;">
<div style="margin-bottom: 0.5rem;">
<div style="margin-bottom: 0.5rem; font-size: 0.7rem; color: #666; text-align: center;">
Found ${displayHeadings.length} heading${displayHeadings.length !== 1 ? 's' : ''}
</div>
${contentsHTML}
</div>
<div style="padding: 0.5rem; border-top: 1px solid #eee; text-align: center;">
<div style="border-top: 1px solid #eee; padding-top: 0.5rem; text-align: center;">
<button onclick="this.closest('.contents-control').contentsControl.refreshContents()"
style="padding: 0.3rem 0.6rem; font-size: 0.7rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;">
🔄 Refresh Contents

View File

@@ -59,7 +59,10 @@ class ControlBase {
this.isDragging = false;
this.isResizing = false;
this.position = { x: 0, y: 0 };
this.size = { width: 300, height: 200 };
this.size = {
width: 300,
height: Math.floor(window.innerHeight / 3)
};
this.originalPosition = null; // Store original position for collapse
// Event handlers storage
@@ -258,6 +261,9 @@ class ControlBase {
panel.style.display = 'block';
toggleBtn.style.display = 'none';
// Calculate default height as 1/3 of window height
const defaultHeight = Math.floor(window.innerHeight / 3);
// Style expanded panel
panel.style.cssText = `
position: relative;
@@ -272,7 +278,7 @@ class ControlBase {
min-height: 200px;
max-height: calc(100vh - 40px);
width: auto;
height: auto;
height: ${defaultHeight}px;
overflow: hidden;
`;
@@ -283,13 +289,13 @@ class ControlBase {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
padding: 4px 12px;
background: rgba(0,0,0,0.05);
border-bottom: 1px solid #dee2e6;
cursor: move;
user-select: none;
flex-shrink: 0;
min-height: 40px;
min-height: 24px;
border-radius: 7px 7px 0 0;
margin: -1px -1px 0 -1px;
`;
@@ -365,7 +371,7 @@ class ControlBase {
// Reset internal size tracking
this.size.width = 300;
this.size.height = 200;
this.size.height = Math.floor(window.innerHeight / 3);
this.storedWidth = null;
// Remove resize handle
@@ -534,8 +540,8 @@ class ControlBase {
resizeHandle.innerHTML = '●'; // Dot resize indicator
resizeHandle.style.cssText = `
position: absolute;
bottom: 4px;
right: 20px;
bottom: 0px;
right: -4px;
width: 12px;
height: 12px;
cursor: se-resize;
@@ -554,6 +560,7 @@ class ControlBase {
// Set up resize handlers
this.addEventListener(resizeHandle, 'mousedown', (e) => this.startResize(e));
this.addEventListener(resizeHandle, 'dblclick', (e) => this.autoResizeToContent(e));
}
}
@@ -647,6 +654,89 @@ class ControlBase {
}
}
/**
* Auto-resize panel to fit content size with viewport repositioning
*/
autoResizeToContent(event) {
return this.safeOperation(() => {
event.preventDefault();
event.stopPropagation();
if (!this.isExpanded) return;
const panel = this.element?.querySelector('.control-panel-expanded');
const contentBody = this.element?.querySelector('.control-content-body');
if (!panel || !contentBody) return;
// Get current panel position
const rect = panel.getBoundingClientRect();
const currentLeft = rect.left;
const currentTop = rect.top;
// Measure content size by temporarily allowing natural sizing
const originalOverflow = contentBody.style.overflow;
const originalMaxHeight = panel.style.maxHeight;
const originalHeight = panel.style.height;
const originalWidth = panel.style.width;
// Temporarily remove constraints to measure natural size
contentBody.style.overflow = 'visible';
panel.style.maxHeight = 'none';
panel.style.height = 'auto';
panel.style.width = 'auto';
// Force reflow and measure
panel.offsetHeight; // Force reflow
const contentRect = contentBody.getBoundingClientRect();
const headerHeight = this.element.querySelector('.control-header')?.offsetHeight || 24;
// Calculate ideal size with padding and margins
const idealWidth = Math.max(300, Math.min(window.innerWidth - 40, contentRect.width + 40));
const idealHeight = Math.max(200, Math.min(window.innerHeight - 40, contentRect.height + headerHeight + 40));
// Restore original constraints
contentBody.style.overflow = originalOverflow;
panel.style.maxHeight = originalMaxHeight;
// Calculate new position to keep panel in viewport
let newLeft = currentLeft;
let newTop = currentTop;
// Adjust position if panel would go outside viewport
if (currentLeft + idealWidth > window.innerWidth) {
newLeft = window.innerWidth - idealWidth - 20;
}
if (newLeft < 20) {
newLeft = 20;
}
if (currentTop + idealHeight > window.innerHeight) {
newTop = window.innerHeight - idealHeight - 20;
}
if (newTop < 20) {
newTop = 20;
}
// Apply new size and position
panel.style.width = `${idealWidth}px`;
panel.style.height = `${idealHeight}px`;
// Update position if it changed
if (newLeft !== currentLeft || newTop !== currentTop) {
this.element.style.left = `${newLeft}px`;
this.element.style.top = `${newTop}px`;
this.position.x = newLeft;
this.position.y = newTop;
}
// Update internal size tracking
this.size.width = idealWidth;
this.size.height = idealHeight;
}, null, 'autoResizeToContent');
}
/**
* Position the control based on compass position (used by show method)
*/
@@ -671,20 +761,11 @@ class ControlBase {
// Apply consistent container styling
content.innerHTML = `
<div class="control-content-title" style="
margin: 0 0 1rem 0;
font-weight: 600;
font-size: 1.1em;
color: #333;
padding: 1rem 1rem 0 1rem;
flex-shrink: 0;
">${this.config.title}</div>
<div class="control-content-container" style="
flex: 1;
overflow-y: auto;
margin: 0 1rem;
padding: 0 0 1rem 0;
margin: 0 0 10px 1rem;
padding: 0.75rem 1rem 1rem 0;
font-size: 0.8rem;
box-sizing: border-box;
min-height: 0;
@@ -692,7 +773,7 @@ class ControlBase {
">
<div class="control-content-body" style="
padding: 0;
margin-bottom: 25px;
margin-bottom: 0;
">
${innerContent}
</div>

View File

@@ -167,10 +167,13 @@ class DebugControl extends ControlBase {
'Not available'
};
// Get Markitect version from config or default
const markitectVersion = window.markitectConfig?.version || 'Unknown';
return `
<div class="system-info" style="margin-bottom: 1rem; padding: 0.5rem; background: #f8f9fa; border-radius: 3px; font-size: 0.7rem;">
<strong>System Information:</strong><br>
<div style="margin-top: 0.3rem; line-height: 1.3;">
<div style="line-height: 1.3;">
<div><strong>Markitect:</strong> ${markitectVersion}</div>
<div><strong>Viewport:</strong> ${systemInfo.viewport}</div>
<div><strong>Screen:</strong> ${systemInfo.screen}</div>
<div><strong>Memory:</strong> ${systemInfo.memory}</div>

View File

@@ -97,25 +97,15 @@
<!-- Initialization Script -->
<script>
window.addEventListener('load', function() {
console.log('🎯 TestDrive JSUI loading complete, initializing...');
console.log('🎯 TestDrive JSUI loading complete');
// Handle CDN loading errors
if (window.markitectMarkedError) {
console.error("CDN library failed to load - network or firewall blocking marked.js");
}
// Initialize main application
try {
if (typeof MarkitectMain !== 'undefined') {
console.log('🚀 Starting MarkitectMain initialization...');
MarkitectMain.initialize();
} else {
console.warn('⚠️ MarkitectMain not available, edit functionality may be limited');
}
} catch (error) {
console.error('❌ TestDrive JSUI initialization failed:', error);
console.log('📄 Content should still be visible in fallback mode');
}
// Note: MarkitectMain auto-initializes via main-updated.js
// No manual initialization needed here
});
</script>
</body>