feat: refactor control panel architecture and fix layout issues
Base class architecture improvements: - Centralize all panel layout, styling, and behavior in ControlBase - Implement consistent generateContent() pattern for subclasses - Add proper flexbox layout with fixed header and scrollable content - Standardize title styling, positioning, and scroll behavior Panel layout fixes: - Fix content positioning to appear inside panels instead of floating above - Implement proper height management (expands with content up to browser height) - Add correct scroll boundaries with only content area scrolling - Position resize handle outside scroll area to avoid scrollbar interference Visual improvements: - Fix rounded border appearance with proper overflow handling - Ensure header respects panel corner radius - Add proper content margins and padding - Improve resize handle positioning and visibility Architecture standardization: - All panels now follow same base class pattern - Individual panels only provide configuration and content generation - Eliminate duplicate styling and layout code across controls - Consistent behavior across all panel types 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -256,33 +256,41 @@ class ContentsControl extends ControlBase {
|
|||||||
* Build the control content
|
* Build the control content
|
||||||
* Override of base class method to provide contents-specific functionality
|
* Override of base class method to provide contents-specific functionality
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
|
||||||
|
/**
|
||||||
|
* Generate contents control content (called by base class buildContent)
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
|
// Extract headings first
|
||||||
|
this.extractHeadings();
|
||||||
|
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
// Extract headings on first build
|
return this.generateContentsHTML();
|
||||||
this.extractHeadings();
|
}, 'Error generating contents', 'generateContent');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate and set content
|
/**
|
||||||
const content = this.element?.querySelector('.control-content');
|
* Override buildContent to add control reference and auto-refresh
|
||||||
if (content) {
|
*/
|
||||||
content.innerHTML = this.generateContentsHTML();
|
buildContent() {
|
||||||
|
super.buildContent();
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
// Store reference to this control for onclick handlers
|
||||||
this.element.contentsControl = this;
|
if (this.element) {
|
||||||
|
this.element.contentsControl = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up auto-refresh for dynamic content
|
||||||
|
if (this.updateInterval) {
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateInterval = setInterval(() => {
|
||||||
|
const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length;
|
||||||
|
if (currentHeadingCount !== this.headings.length) {
|
||||||
|
this.refreshContents();
|
||||||
}
|
}
|
||||||
|
}, 5000); // Check every 5 seconds
|
||||||
// Set up auto-refresh for dynamic content
|
|
||||||
if (this.updateInterval) {
|
|
||||||
clearInterval(this.updateInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateInterval = setInterval(() => {
|
|
||||||
const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length;
|
|
||||||
if (currentHeadingCount !== this.headings.length) {
|
|
||||||
this.refreshContents();
|
|
||||||
}
|
|
||||||
}, 5000); // Check every 5 seconds
|
|
||||||
|
|
||||||
}, null, 'buildContent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -260,6 +260,9 @@ class ControlBase {
|
|||||||
|
|
||||||
// Style expanded panel
|
// Style expanded panel
|
||||||
panel.style.cssText = `
|
panel.style.cssText = `
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: rgba(248, 249, 250, 0.95);
|
background: rgba(248, 249, 250, 0.95);
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -267,6 +270,10 @@ class ControlBase {
|
|||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Style header
|
// Style header
|
||||||
@@ -281,6 +288,22 @@ class ControlBase {
|
|||||||
border-bottom: 1px solid #dee2e6;
|
border-bottom: 1px solid #dee2e6;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 7px 7px 0 0;
|
||||||
|
margin: -1px -1px 0 -1px;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style content area container
|
||||||
|
const contentArea = this.element.querySelector('.control-content');
|
||||||
|
if (contentArea) {
|
||||||
|
contentArea.style.cssText = `
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,15 +535,16 @@ class ControlBase {
|
|||||||
resizeHandle.style.cssText = `
|
resizeHandle.style.cssText = `
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
right: 4px;
|
right: 20px;
|
||||||
width: 8px;
|
width: 12px;
|
||||||
height: 8px;
|
height: 12px;
|
||||||
cursor: se-resize;
|
cursor: se-resize;
|
||||||
font-size: 8px;
|
font-size: 10px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: #999;
|
color: #999;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
z-index: 10;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add to the expanded panel
|
// Add to the expanded panel
|
||||||
@@ -636,14 +660,55 @@ class ControlBase {
|
|||||||
/**
|
/**
|
||||||
* Build the control content (to be overridden by subclasses)
|
* Build the control content (to be overridden by subclasses)
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Build content with consistent styling - calls subclass generateContent()
|
||||||
|
*/
|
||||||
buildContent() {
|
buildContent() {
|
||||||
// Default implementation - subclasses should override this
|
|
||||||
const content = this.element?.querySelector('.control-content');
|
const content = this.element?.querySelector('.control-content');
|
||||||
if (content) {
|
if (content) {
|
||||||
content.innerHTML = this.config.defaultContent;
|
// Get content from subclass
|
||||||
|
const innerContent = this.generateContent ? this.generateContent() : this.config.defaultContent;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 0;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
">
|
||||||
|
<div class="control-content-body" style="
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
">
|
||||||
|
${innerContent}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate content - subclasses should override this method
|
||||||
|
* @returns {string} HTML content for the panel body
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
|
return this.config.defaultContent || `<p>Panel content goes here...</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the control
|
* Show the control
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -423,35 +423,36 @@ class DebugControl extends ControlBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the control content
|
* Generate debug control content (called by base class buildContent)
|
||||||
* Override of base class method to provide debug-specific functionality
|
*/
|
||||||
|
generateContent() {
|
||||||
|
return this.safeOperation(() => {
|
||||||
|
return `
|
||||||
|
${this.generateSystemInfoHTML()}
|
||||||
|
${this.generatePerformanceHTML()}
|
||||||
|
${this.generateFilterControlsHTML()}
|
||||||
|
${this.generateMessagesHTML()}
|
||||||
|
${this.generateControlButtonsHTML()}
|
||||||
|
|
||||||
|
<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #eee; font-size: 0.7rem; color: #666; text-align: center;">
|
||||||
|
Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} |
|
||||||
|
Filter: ${this.messageFilter.toUpperCase()} |
|
||||||
|
Messages: ${this.getFilteredMessages().length}/${this.messages.length}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}, 'Error generating debug content', 'generateContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override buildContent to add control reference
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
buildContent() {
|
||||||
return this.safeOperation(() => {
|
super.buildContent();
|
||||||
const content = this.element?.querySelector('.control-content');
|
|
||||||
if (content) {
|
|
||||||
content.innerHTML = `
|
|
||||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
|
||||||
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Debug Information</div>
|
|
||||||
|
|
||||||
${this.generateSystemInfoHTML()}
|
// Store reference to this control for onclick handlers
|
||||||
${this.generatePerformanceHTML()}
|
if (this.element) {
|
||||||
${this.generateFilterControlsHTML()}
|
this.element.debugControl = this;
|
||||||
${this.generateMessagesHTML()}
|
}
|
||||||
${this.generateControlButtonsHTML()}
|
|
||||||
|
|
||||||
<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #eee; font-size: 0.7rem; color: #666; text-align: center;">
|
|
||||||
Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} |
|
|
||||||
Filter: ${this.messageFilter.toUpperCase()} |
|
|
||||||
Messages: ${this.getFilteredMessages().length}/${this.messages.length}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
|
||||||
this.element.debugControl = this;
|
|
||||||
}
|
|
||||||
}, null, 'buildContent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,10 +67,7 @@ class EditControl extends ControlBase {
|
|||||||
generateEditToolsHTML() {
|
generateEditToolsHTML() {
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
return `
|
return `
|
||||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
<!-- Document Actions -->
|
||||||
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Edit Tools</div>
|
|
||||||
|
|
||||||
<!-- Document Actions -->
|
|
||||||
<div class="action-section" style="margin-bottom: 1rem;">
|
<div class="action-section" style="margin-bottom: 1rem;">
|
||||||
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Document Actions</div>
|
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Document Actions</div>
|
||||||
|
|
||||||
@@ -180,7 +177,6 @@ class EditControl extends ControlBase {
|
|||||||
${this.unsavedChanges ? '<div style="color: #dc3545;">⚠️ Unsaved changes</div>' : ''}
|
${this.unsavedChanges ? '<div style="color: #dc3545;">⚠️ Unsaved changes</div>' : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}, '<p>Error generating edit tools</p>', 'generateEditToolsHTML');
|
}, '<p>Error generating edit tools</p>', 'generateEditToolsHTML');
|
||||||
@@ -539,16 +535,25 @@ class EditControl extends ControlBase {
|
|||||||
* Build the control content
|
* Build the control content
|
||||||
* Override of base class method to provide edit-specific functionality
|
* Override of base class method to provide edit-specific functionality
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
/**
|
||||||
|
* Generate edit control content (called by base class buildContent)
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
const content = this.element?.querySelector('.control-content');
|
return this.generateEditToolsHTML();
|
||||||
if (content) {
|
}, 'Error generating edit content', 'generateContent');
|
||||||
content.innerHTML = this.generateEditToolsHTML();
|
}
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
/**
|
||||||
this.element.editControl = this;
|
* Override buildContent to add control reference
|
||||||
}
|
*/
|
||||||
}, null, 'buildContent');
|
buildContent() {
|
||||||
|
super.buildContent();
|
||||||
|
|
||||||
|
// Store reference to this control for onclick handlers
|
||||||
|
if (this.element) {
|
||||||
|
this.element.editControl = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ class StatusControl extends ControlBase {
|
|||||||
const formatNumber = (num) => num.toLocaleString();
|
const formatNumber = (num) => num.toLocaleString();
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
<div class="stats-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem;">
|
||||||
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Document Statistics</div>
|
|
||||||
|
|
||||||
<div class="stats-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem;">
|
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<strong>Words:</strong><br>
|
<strong>Words:</strong><br>
|
||||||
<span style="font-size: 1.1em; color: #007bff;">${formatNumber(this.stats.words)}</span>
|
<span style="font-size: 1.1em; color: #007bff;">${formatNumber(this.stats.words)}</span>
|
||||||
@@ -206,7 +203,6 @@ class StatusControl extends ControlBase {
|
|||||||
Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
|
Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}, '<p>Error displaying statistics</p>', 'formatStatistics');
|
}, '<p>Error displaying statistics</p>', 'formatStatistics');
|
||||||
@@ -322,30 +318,37 @@ class StatusControl extends ControlBase {
|
|||||||
* Build the control content
|
* Build the control content
|
||||||
* Override of base class method to provide status-specific functionality
|
* Override of base class method to provide status-specific functionality
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
/**
|
||||||
|
* Generate status control content (called by base class buildContent)
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
|
// Analyze document first
|
||||||
|
this.analyzeDocument();
|
||||||
|
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
// Analyze document first
|
return this.formatStatistics();
|
||||||
this.analyzeDocument();
|
}, 'Error generating status content', 'generateContent');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate and set content
|
/**
|
||||||
const content = this.element?.querySelector('.control-content');
|
* Override buildContent to add control reference and auto-refresh
|
||||||
if (content) {
|
*/
|
||||||
content.innerHTML = this.formatStatistics();
|
buildContent() {
|
||||||
|
super.buildContent();
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
// Store reference to this control for onclick handlers
|
||||||
this.element.statusControl = this;
|
if (this.element) {
|
||||||
}
|
this.element.statusControl = this;
|
||||||
|
}
|
||||||
|
|
||||||
// Set up auto-refresh for dynamic content
|
// Set up auto-refresh for dynamic content
|
||||||
if (this.updateInterval) {
|
if (this.updateInterval) {
|
||||||
clearInterval(this.updateInterval);
|
clearInterval(this.updateInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateInterval = setInterval(() => {
|
this.updateInterval = setInterval(() => {
|
||||||
this.refreshStats();
|
this.refreshStats();
|
||||||
}, 10000); // Update every 10 seconds
|
}, 10000); // Update every 10 seconds
|
||||||
|
|
||||||
}, null, 'buildContent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -256,33 +256,41 @@ class ContentsControl extends ControlBase {
|
|||||||
* Build the control content
|
* Build the control content
|
||||||
* Override of base class method to provide contents-specific functionality
|
* Override of base class method to provide contents-specific functionality
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
|
||||||
|
/**
|
||||||
|
* Generate contents control content (called by base class buildContent)
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
|
// Extract headings first
|
||||||
|
this.extractHeadings();
|
||||||
|
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
// Extract headings on first build
|
return this.generateContentsHTML();
|
||||||
this.extractHeadings();
|
}, 'Error generating contents', 'generateContent');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate and set content
|
/**
|
||||||
const content = this.element?.querySelector('.control-content');
|
* Override buildContent to add control reference and auto-refresh
|
||||||
if (content) {
|
*/
|
||||||
content.innerHTML = this.generateContentsHTML();
|
buildContent() {
|
||||||
|
super.buildContent();
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
// Store reference to this control for onclick handlers
|
||||||
this.element.contentsControl = this;
|
if (this.element) {
|
||||||
|
this.element.contentsControl = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up auto-refresh for dynamic content
|
||||||
|
if (this.updateInterval) {
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateInterval = setInterval(() => {
|
||||||
|
const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length;
|
||||||
|
if (currentHeadingCount !== this.headings.length) {
|
||||||
|
this.refreshContents();
|
||||||
}
|
}
|
||||||
|
}, 5000); // Check every 5 seconds
|
||||||
// Set up auto-refresh for dynamic content
|
|
||||||
if (this.updateInterval) {
|
|
||||||
clearInterval(this.updateInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateInterval = setInterval(() => {
|
|
||||||
const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length;
|
|
||||||
if (currentHeadingCount !== this.headings.length) {
|
|
||||||
this.refreshContents();
|
|
||||||
}
|
|
||||||
}, 5000); // Check every 5 seconds
|
|
||||||
|
|
||||||
}, null, 'buildContent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -260,6 +260,9 @@ class ControlBase {
|
|||||||
|
|
||||||
// Style expanded panel
|
// Style expanded panel
|
||||||
panel.style.cssText = `
|
panel.style.cssText = `
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: rgba(248, 249, 250, 0.95);
|
background: rgba(248, 249, 250, 0.95);
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -267,6 +270,10 @@ class ControlBase {
|
|||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Style header
|
// Style header
|
||||||
@@ -281,6 +288,22 @@ class ControlBase {
|
|||||||
border-bottom: 1px solid #dee2e6;
|
border-bottom: 1px solid #dee2e6;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 7px 7px 0 0;
|
||||||
|
margin: -1px -1px 0 -1px;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style content area container
|
||||||
|
const contentArea = this.element.querySelector('.control-content');
|
||||||
|
if (contentArea) {
|
||||||
|
contentArea.style.cssText = `
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,15 +535,16 @@ class ControlBase {
|
|||||||
resizeHandle.style.cssText = `
|
resizeHandle.style.cssText = `
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
right: 4px;
|
right: 20px;
|
||||||
width: 8px;
|
width: 12px;
|
||||||
height: 8px;
|
height: 12px;
|
||||||
cursor: se-resize;
|
cursor: se-resize;
|
||||||
font-size: 8px;
|
font-size: 10px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: #999;
|
color: #999;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
z-index: 10;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add to the expanded panel
|
// Add to the expanded panel
|
||||||
@@ -636,14 +660,55 @@ class ControlBase {
|
|||||||
/**
|
/**
|
||||||
* Build the control content (to be overridden by subclasses)
|
* Build the control content (to be overridden by subclasses)
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Build content with consistent styling - calls subclass generateContent()
|
||||||
|
*/
|
||||||
buildContent() {
|
buildContent() {
|
||||||
// Default implementation - subclasses should override this
|
|
||||||
const content = this.element?.querySelector('.control-content');
|
const content = this.element?.querySelector('.control-content');
|
||||||
if (content) {
|
if (content) {
|
||||||
content.innerHTML = this.config.defaultContent;
|
// Get content from subclass
|
||||||
|
const innerContent = this.generateContent ? this.generateContent() : this.config.defaultContent;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 0;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
">
|
||||||
|
<div class="control-content-body" style="
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
">
|
||||||
|
${innerContent}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate content - subclasses should override this method
|
||||||
|
* @returns {string} HTML content for the panel body
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
|
return this.config.defaultContent || `<p>Panel content goes here...</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the control
|
* Show the control
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -423,35 +423,36 @@ class DebugControl extends ControlBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the control content
|
* Generate debug control content (called by base class buildContent)
|
||||||
* Override of base class method to provide debug-specific functionality
|
*/
|
||||||
|
generateContent() {
|
||||||
|
return this.safeOperation(() => {
|
||||||
|
return `
|
||||||
|
${this.generateSystemInfoHTML()}
|
||||||
|
${this.generatePerformanceHTML()}
|
||||||
|
${this.generateFilterControlsHTML()}
|
||||||
|
${this.generateMessagesHTML()}
|
||||||
|
${this.generateControlButtonsHTML()}
|
||||||
|
|
||||||
|
<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #eee; font-size: 0.7rem; color: #666; text-align: center;">
|
||||||
|
Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} |
|
||||||
|
Filter: ${this.messageFilter.toUpperCase()} |
|
||||||
|
Messages: ${this.getFilteredMessages().length}/${this.messages.length}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}, 'Error generating debug content', 'generateContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override buildContent to add control reference
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
buildContent() {
|
||||||
return this.safeOperation(() => {
|
super.buildContent();
|
||||||
const content = this.element?.querySelector('.control-content');
|
|
||||||
if (content) {
|
|
||||||
content.innerHTML = `
|
|
||||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
|
||||||
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Debug Information</div>
|
|
||||||
|
|
||||||
${this.generateSystemInfoHTML()}
|
// Store reference to this control for onclick handlers
|
||||||
${this.generatePerformanceHTML()}
|
if (this.element) {
|
||||||
${this.generateFilterControlsHTML()}
|
this.element.debugControl = this;
|
||||||
${this.generateMessagesHTML()}
|
}
|
||||||
${this.generateControlButtonsHTML()}
|
|
||||||
|
|
||||||
<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #eee; font-size: 0.7rem; color: #666; text-align: center;">
|
|
||||||
Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} |
|
|
||||||
Filter: ${this.messageFilter.toUpperCase()} |
|
|
||||||
Messages: ${this.getFilteredMessages().length}/${this.messages.length}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
|
||||||
this.element.debugControl = this;
|
|
||||||
}
|
|
||||||
}, null, 'buildContent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,10 +67,7 @@ class EditControl extends ControlBase {
|
|||||||
generateEditToolsHTML() {
|
generateEditToolsHTML() {
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
return `
|
return `
|
||||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
<!-- Document Actions -->
|
||||||
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Edit Tools</div>
|
|
||||||
|
|
||||||
<!-- Document Actions -->
|
|
||||||
<div class="action-section" style="margin-bottom: 1rem;">
|
<div class="action-section" style="margin-bottom: 1rem;">
|
||||||
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Document Actions</div>
|
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Document Actions</div>
|
||||||
|
|
||||||
@@ -180,7 +177,6 @@ class EditControl extends ControlBase {
|
|||||||
${this.unsavedChanges ? '<div style="color: #dc3545;">⚠️ Unsaved changes</div>' : ''}
|
${this.unsavedChanges ? '<div style="color: #dc3545;">⚠️ Unsaved changes</div>' : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}, '<p>Error generating edit tools</p>', 'generateEditToolsHTML');
|
}, '<p>Error generating edit tools</p>', 'generateEditToolsHTML');
|
||||||
@@ -539,16 +535,25 @@ class EditControl extends ControlBase {
|
|||||||
* Build the control content
|
* Build the control content
|
||||||
* Override of base class method to provide edit-specific functionality
|
* Override of base class method to provide edit-specific functionality
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
/**
|
||||||
|
* Generate edit control content (called by base class buildContent)
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
const content = this.element?.querySelector('.control-content');
|
return this.generateEditToolsHTML();
|
||||||
if (content) {
|
}, 'Error generating edit content', 'generateContent');
|
||||||
content.innerHTML = this.generateEditToolsHTML();
|
}
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
/**
|
||||||
this.element.editControl = this;
|
* Override buildContent to add control reference
|
||||||
}
|
*/
|
||||||
}, null, 'buildContent');
|
buildContent() {
|
||||||
|
super.buildContent();
|
||||||
|
|
||||||
|
// Store reference to this control for onclick handlers
|
||||||
|
if (this.element) {
|
||||||
|
this.element.editControl = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ class StatusControl extends ControlBase {
|
|||||||
const formatNumber = (num) => num.toLocaleString();
|
const formatNumber = (num) => num.toLocaleString();
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
<div class="stats-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem;">
|
||||||
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Document Statistics</div>
|
|
||||||
|
|
||||||
<div class="stats-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem;">
|
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<strong>Words:</strong><br>
|
<strong>Words:</strong><br>
|
||||||
<span style="font-size: 1.1em; color: #007bff;">${formatNumber(this.stats.words)}</span>
|
<span style="font-size: 1.1em; color: #007bff;">${formatNumber(this.stats.words)}</span>
|
||||||
@@ -206,7 +203,6 @@ class StatusControl extends ControlBase {
|
|||||||
Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
|
Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}, '<p>Error displaying statistics</p>', 'formatStatistics');
|
}, '<p>Error displaying statistics</p>', 'formatStatistics');
|
||||||
@@ -322,30 +318,37 @@ class StatusControl extends ControlBase {
|
|||||||
* Build the control content
|
* Build the control content
|
||||||
* Override of base class method to provide status-specific functionality
|
* Override of base class method to provide status-specific functionality
|
||||||
*/
|
*/
|
||||||
buildContent() {
|
/**
|
||||||
|
* Generate status control content (called by base class buildContent)
|
||||||
|
*/
|
||||||
|
generateContent() {
|
||||||
|
// Analyze document first
|
||||||
|
this.analyzeDocument();
|
||||||
|
|
||||||
return this.safeOperation(() => {
|
return this.safeOperation(() => {
|
||||||
// Analyze document first
|
return this.formatStatistics();
|
||||||
this.analyzeDocument();
|
}, 'Error generating status content', 'generateContent');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate and set content
|
/**
|
||||||
const content = this.element?.querySelector('.control-content');
|
* Override buildContent to add control reference and auto-refresh
|
||||||
if (content) {
|
*/
|
||||||
content.innerHTML = this.formatStatistics();
|
buildContent() {
|
||||||
|
super.buildContent();
|
||||||
|
|
||||||
// Store reference to this control for onclick handlers
|
// Store reference to this control for onclick handlers
|
||||||
this.element.statusControl = this;
|
if (this.element) {
|
||||||
}
|
this.element.statusControl = this;
|
||||||
|
}
|
||||||
|
|
||||||
// Set up auto-refresh for dynamic content
|
// Set up auto-refresh for dynamic content
|
||||||
if (this.updateInterval) {
|
if (this.updateInterval) {
|
||||||
clearInterval(this.updateInterval);
|
clearInterval(this.updateInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateInterval = setInterval(() => {
|
this.updateInterval = setInterval(() => {
|
||||||
this.refreshStats();
|
this.refreshStats();
|
||||||
}, 10000); // Update every 10 seconds
|
}, 10000); // Update every 10 seconds
|
||||||
|
|
||||||
}, null, 'buildContent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user