This commit is contained in:
Juergen Kunz
2025-06-27 20:40:06 +00:00
parent 88ff74bb86
commit 65acda3de1
4 changed files with 348 additions and 63 deletions

View File

@ -58,6 +58,34 @@ Updated the WCC Dashboard UI components (properties and sidebar) to use shadcn-l
- Grid layout with 1px gaps creating subtle dividers - Grid layout with 1px gaps creating subtle dividers
- Warning display with backdrop blur and rounded corners - Warning display with backdrop blur and rounded corners
## Advanced Complex Properties Editor (2025-06-27)
### Overview
Implemented an advanced editor for complex properties (Arrays and Objects) that appears between the wcc-properties panel and frame when activated.
### Features
1. **Dynamic Layout**: Frame shrinks by 300px from bottom when editor opens
2. **JSON Editor**:
- Monospace font for code editing
- Tab key support for indentation
- Syntax validation with error messages
- Live preview of changes
3. **Smooth Transitions**: Animated opening/closing with 0.3s ease
4. **Error Handling**: Invalid JSON shows clear error messages that disappear on typing
### Technical Implementation
- **State Management**: Added showAdvancedEditor, editingProperty, editorValue, editorError states
- **Event System**: Uses custom 'editorStateChanged' event to communicate with parent dashboard
- **Dynamic Styling**: wcc-frame's bottom position changes from 100px to 400px when editor is open
- **Property Types**: Object and Array properties show "Edit Object/Array" button instead of inline controls
### User Flow
1. Click "Edit Object/Array" button on complex property
2. Editor slides up between properties panel and frame
3. Edit JSON with live validation
4. Save applies changes and refreshes properties, Cancel discards changes
5. Frame automatically resizes back when editor closes
## Properties Panel Element Detection Issue (Fixed) ## Properties Panel Element Detection Issue (Fixed)
### Problem ### Problem

View File

@ -99,6 +99,13 @@ export class WccDashboard extends DeesElement {
@selectedTheme=${(eventArg) => { @selectedTheme=${(eventArg) => {
this.selectedTheme = eventArg.detail; this.selectedTheme = eventArg.detail;
}} }}
@editorStateChanged=${async (eventArg) => {
const frame = await this.wccFrame;
if (frame) {
frame.advancedEditorOpen = eventArg.detail.isOpen;
frame.requestUpdate();
}
}}
></wcc-properties> ></wcc-properties>
<wcc-frame id="wccFrame" viewport=${this.selectedViewport}> <wcc-frame id="wccFrame" viewport=${this.selectedViewport}>
</wcc-frame> </wcc-frame>

View File

@ -13,6 +13,9 @@ export class WccFrame extends DeesElement {
@property() @property()
public viewport: string; public viewport: string;
@property({ type: Boolean })
public advancedEditorOpen: boolean = false;
public static styles = [ public static styles = [
css` css`
:host { :host {
@ -22,7 +25,6 @@ export class WccFrame extends DeesElement {
left: 200px; left: 200px;
right: 0px; right: 0px;
top: 0px; top: 0px;
bottom: 100px;
overflow-y: auto; overflow-y: auto;
overflow-x: auto; overflow-x: auto;
overscroll-behavior: contain; overscroll-behavior: contain;
@ -41,6 +43,8 @@ export class WccFrame extends DeesElement {
return html` return html`
<style> <style>
:host { :host {
bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
transition: bottom 0.3s ease;
${(() => { ${(() => {
switch (this.viewport) { switch (this.viewport) {
case 'desktop': case 'desktop':

View File

@ -34,6 +34,20 @@ export class WccProperties extends DeesElement {
@state() @state()
propertyContent: TemplateResult[] = []; propertyContent: TemplateResult[] = [];
@state()
showAdvancedEditor: boolean = false;
@state()
editingProperty: { name: string; value: any; element: HTMLElement } = null;
@state()
editorValue: string = '';
@state()
editorError: string = '';
public editorHeight: number = 300;
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`
<style> <style>
@ -62,7 +76,7 @@ export class WccProperties extends DeesElement {
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
left: 200px; left: 200px;
height: 100px; height: ${this.showAdvancedEditor ? 100 + this.editorHeight : 100}px;
bottom: 0px; bottom: 0px;
right: 0px; right: 0px;
overflow: hidden; overflow: hidden;
@ -74,6 +88,10 @@ export class WccProperties extends DeesElement {
grid-template-columns: 1fr 150px 300px 70px; grid-template-columns: 1fr 150px 300px 70px;
height: 100%; height: 100%;
} }
.main-content .grid {
height: 100px;
}
.properties { .properties {
background: transparent; background: transparent;
overflow-y: auto; overflow-y: auto;
@ -150,6 +168,23 @@ export class WccProperties extends DeesElement {
accent-color: var(--primary); accent-color: var(--primary);
} }
.properties .editor-button {
padding: 0.25rem 0.5rem;
background: var(--input);
border: 1px solid transparent;
border-radius: var(--radius-sm);
color: var(--foreground);
font-size: 0.7rem;
cursor: pointer;
transition: all 0.15s ease;
text-align: center;
}
.properties .editor-button:hover {
border-color: var(--primary);
background: rgba(59, 130, 246, 0.1);
}
.viewportSelector, .viewportSelector,
.themeSelector { .themeSelector {
user-select: none; user-select: none;
@ -254,7 +289,148 @@ export class WccProperties extends DeesElement {
border: 1px solid var(--border); border: 1px solid var(--border);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
} }
.advanced-editor {
position: absolute;
left: 0;
right: 0;
top: 0;
height: ${this.editorHeight}px;
background: var(--background);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
.editor-header {
padding: 0.75rem 1rem;
background: rgba(59, 130, 246, 0.03);
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.editor-title {
font-size: 0.875rem;
font-weight: 500;
color: var(--foreground);
}
.editor-actions {
display: flex;
gap: 0.5rem;
}
.editor-button {
padding: 0.375rem 0.75rem;
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius);
color: #999;
font-size: 0.75rem;
cursor: pointer;
transition: all 0.15s ease;
}
.editor-button:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--foreground);
}
.editor-button.primary {
background: var(--primary);
color: var(--primary-foreground);
border-color: var(--primary);
}
.editor-button.primary:hover {
background: rgba(59, 130, 246, 0.8);
}
.editor-content {
flex: 1;
padding: 1rem;
overflow: auto;
}
.editor-textarea {
width: 100%;
height: 100%;
background: var(--input);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--foreground);
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.875rem;
padding: 0.75rem;
resize: none;
outline: none;
transition: all 0.15s ease;
}
.editor-textarea:focus {
border-color: var(--primary);
background: rgba(59, 130, 246, 0.05);
}
.editor-error {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: var(--radius-sm);
color: #f87171;
font-size: 0.75rem;
}
.main-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 100px;
display: grid;
grid-template-columns: 1fr 150px 300px 70px;
}
</style> </style>
${this.showAdvancedEditor ? html`
<div class="advanced-editor">
<div class="editor-header">
<div class="editor-title">Editing: ${this.editingProperty?.name}</div>
<div class="editor-actions">
<button class="editor-button" @click=${this.handleEditorCancel}>Cancel</button>
<button class="editor-button primary" @click=${this.handleEditorSave}>Save</button>
</div>
</div>
<div class="editor-content">
<textarea
class="editor-textarea"
.value=${this.editorValue}
@input=${(e: InputEvent) => {
this.editorValue = (e.target as HTMLTextAreaElement).value;
this.editorError = '';
}}
@keydown=${(e: KeyboardEvent) => {
if (e.key === 'Tab') {
e.preventDefault();
const target = e.target as HTMLTextAreaElement;
const start = target.selectionStart;
const end = target.selectionEnd;
const value = target.value;
target.value = value.substring(0, start) + ' ' + value.substring(end);
target.selectionStart = target.selectionEnd = start + 2;
}
}}
></textarea>
${this.editorError ? html`
<div class="editor-error">${this.editorError}</div>
` : null}
</div>
</div>
` : null}
<div class="main-content">
<div class="grid"> <div class="grid">
<div class="properties"> <div class="properties">
${this.propertyContent} ${this.propertyContent}
@ -323,6 +499,7 @@ export class WccProperties extends DeesElement {
<div class="docs">Docs</div> <div class="docs">Docs</div>
</div> </div>
${this.warning ? html`<div class="warning">${this.warning}</div>` : null} ${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
</div>
`; `;
} }
@ -501,6 +678,17 @@ export class WccProperties extends DeesElement {
`; `;
})} })}
</select>`; </select>`;
case 'Object':
case 'Array':
return html`<button
class="editor-button"
style="width: 100%; margin-top: 0.25rem;"
@click="${() => this.openAdvancedEditor(key, firstFoundInstantiatedElement[key], firstFoundInstantiatedElement)}"
>
Edit ${propertyTypeString}
</button>`;
default:
return html`<div style="color: #666; font-size: 0.7rem;">Unsupported type</div>`;
} }
})()} })()}
</div> </div>
@ -552,4 +740,62 @@ export class WccProperties extends DeesElement {
); );
this.dashboardRef.buildUrl(); this.dashboardRef.buildUrl();
} }
private openAdvancedEditor(propertyName: string, value: any, element: HTMLElement) {
this.editingProperty = {
name: propertyName,
value: value,
element: element
};
this.editorValue = JSON.stringify(value, null, 2);
this.editorError = '';
this.showAdvancedEditor = true;
// Notify parent to resize frame
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: true },
bubbles: true
})
);
}
private handleEditorSave() {
try {
const parsedValue = JSON.parse(this.editorValue);
if (this.editingProperty) {
this.editingProperty.element[this.editingProperty.name] = parsedValue;
this.showAdvancedEditor = false;
this.editorError = '';
// Notify parent to resize frame back
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
// Refresh properties display
this.createProperties();
}
} catch (error) {
this.editorError = `Invalid JSON: ${error.message}`;
}
}
private handleEditorCancel() {
this.showAdvancedEditor = false;
this.editingProperty = null;
this.editorValue = '';
this.editorError = '';
// Notify parent to resize frame back
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
} }