Compare commits

..

16 Commits

Author SHA1 Message Date
923bedc4fc 1.1.0
Some checks failed
Default (tags) / security (push) Failing after 25s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-27 20:52:27 +00:00
e8b771bde4 feat(wcctools): Enhance component tools with an advanced property editor, improved element detection and modernized UI styling for a more responsive dashboard experience. 2025-06-27 20:52:27 +00:00
7a248993bc update 2025-06-27 20:51:31 +00:00
03f215e0f1 update 2025-06-27 20:50:32 +00:00
216cb0288d update 2025-06-27 20:41:24 +00:00
65acda3de1 update 2025-06-27 20:40:06 +00:00
88ff74bb86 update styling 2025-06-27 20:28:47 +00:00
98a5b1b5a3 update 2025-06-27 20:26:42 +00:00
bbf738d4e2 update 2025-06-27 20:22:28 +00:00
4f8ca7061a update 2025-06-27 20:03:54 +00:00
d26d99dbff 1.0.101
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-26 23:11:58 +00:00
c1d8e347de fix(wcc-dashboard): Improve scroll listener management and add new test pages 2025-06-26 23:11:58 +00:00
b6c41caf44 1.0.100
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-26 22:52:54 +00:00
b858b3b9e2 fix(wcc-dashboard): Prevent duplicate application of scroll positions in dashboard to avoid interfering with user scrolling 2025-06-26 22:52:54 +00:00
4ed37086ae 1.0.99
Some checks failed
Default (tags) / security (push) Failing after 20s
Default (tags) / test (push) Failing after 20s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-26 20:18:20 +00:00
b4c0de47b9 fix(dashboard): Fix scroll state preservation in dashboard by tracking frame and sidebar scroll positions and updating the URL accordingly. 2025-06-26 20:18:20 +00:00
13 changed files with 1165 additions and 150 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@
# artifacts # artifacts
coverage/ coverage/
public/ public/
pages/
# installs # installs
node_modules/ node_modules/

57
changelog.md Normal file
View File

@ -0,0 +1,57 @@
# Changelog
## 2025-06-27 - 1.1.0 - feat(wcctools)
Enhance component tools with an advanced property editor, improved element detection and modernized UI styling for a more responsive dashboard experience.
- Updated documentation and in-code hints with new shadcn-like design patterns for the dashboard UI.
- Introduced an advanced complex properties editor supporting JSON validation and multi-editor handling.
- Refined recursive element search in the properties panel to improve asynchronous rendering detection.
- Expanded test coverage with scenarios for edge cases, nested elements and wrapper components.
## 2025-06-26 - 1.0.101 - fix(wcc-dashboard)
Improve scroll listener management and add new test pages
- Removed the pages/ directory entry from .gitignore to allow test pages to be tracked
- Added new test pages: page1 and pageLongScroll for enhanced scroll and navigation testing
- Refactored wcc-dashboard: changed scroll position properties to private variables and added a flag to prevent duplicate scroll listener attachment
## 2025-06-26 - 1.0.100 - fix(wcc-dashboard)
Prevent duplicate application of scroll positions in dashboard to avoid interfering with user scrolling
- Added a private 'scrollPositionsApplied' property to track if scroll positions have already been applied
- Introduced a guard in the applyScrollPositions method to ensure the scroll state is applied only once
## 2025-06-26 - 1.0.99 - fix(dashboard)
Fix scroll state preservation in dashboard by tracking frame and sidebar scroll positions and updating the URL accordingly.
- Added frameScrollY and sidebarScrollY properties to capture scroll positions.
- Set up scroll listeners on wcc-frame and wcc-sidebar to update scroll state.
- Implemented debounced updates to modify the URL with current scroll positions without navigation.
- Restored scroll positions from URL query parameters during initialization.
## 2025-06-16 - 1.0.97 - properties-panel
- Improve element detection timing and value handling in properties panel
## 2025-06-16 - 1.0.96 - properties-panel
- Enhance element detection and error handling for nested structures
## 2025-06-16 - 1.0.95 - package
- Correct path for demotools export in package.json
## 2025-06-16 - 1.0.94 - demotools
- Enhance runAfterRender to provide full DOM API access and improve element selection
## 2025-06-16 - 1.0.92 - demotools
- Update DeesDemoWrapper to handle multiple slotted elements in runAfterRender callback
## 2025-06-16 - 1.0.91 - readme
- Update documentation with comprehensive overview, quick start guide, and detailed feature descriptions
## 2025-06-16 - 1.0.90 - demo/properties/refactor
- Add DeesDemoWrapper component for enhanced demo element handling
- Enhance element detection in properties panel with recursive search and retry mechanism
- Refactor code structure for improved readability and maintainability
## 2024-05-06 to 2020-05-10 - 1.0.891.0.17 - core
- Over a series of releases, trivial core fixes and updates were applied.
- (Note: Version 1.0.87 also included an update to the documentation.)

View File

@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-wcctools", "name": "@design.estate/dees-wcctools",
"version": "1.0.98", "version": "1.1.0",
"private": false, "private": false,
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.", "description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
"exports": { "exports": {

View File

@ -1,5 +1,107 @@
# Project Hints and Findings # Project Hints and Findings
## UI Redesign with Shadcn-like Styles (2025-06-27)
### Changes Made
Updated the WCC Dashboard UI components (properties and sidebar) to use shadcn-like design patterns:
1. **Color System**: Implemented CSS variables for theming:
- `--background`, `--foreground`, `--card`, `--primary`, `--secondary`
- `--muted`, `--accent`, `--border`, `--input`, `--ring`
- Consistent dark theme with subtle borders and proper contrast
- Dynamic theme switching between light and dark modes
2. **Properties Panel Improvements (Updated)**:
- Changed from fixed 3-column grid to flexible flexbox layout
- Properties now wrap and use space more efficiently
- Added rounded corners (using --radius-md) and better spacing
- Property items use flexbox with min-width for responsive layout
- Property labels now show as styled headers with type info
- Form controls updated with shadcn-style focus states and transitions
- Complex properties (Objects/Arrays) show "Edit" button
- Advanced JSON editor appears above properties panel when editing complex types
- Dynamic height adjustment (50px when editor is open, 120px normally)
3. **Sidebar Styling**:
- Updated with consistent color scheme
- Added rounded corners to menu items
- Improved hover states with smooth transitions
- Better typography with proper font weights
4. **Advanced Property Editor**:
- JSON editor for complex types (Objects and Arrays)
- Monaco-style monospace font for code editing
- Live updates to element properties
- Positioned above the properties panel with smooth transitions
5. **Theme and Viewport Selectors (New)**:
- Redesigned buttons with flexbox layout for better icon/text alignment
- Added hover effects with transform and shadow
- Smooth transitions on all interactive elements
- Selected state uses primary color variables
- Icons reduced in size for better balance
6. **Form Controls (New)**:
- Input fields and selects now have:
- Rounded corners (--radius-sm)
- Consistent padding (0.5rem 0.75rem)
- Focus states with ring effect using box-shadow
- Smooth transition animations
- Checkboxes use accent-color for theming
### Technical Details
- Uses system font stack ('Inter' preferred) for better native appearance
- Subtle borders with CSS variables for consistency
- Consistent spacing using rem units
- Smooth transitions (0.2s ease) for interactive elements
- Custom scrollbar styling for better visual integration
- Grid layout with 1px gaps creating subtle dividers
- 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. **Multiple Editors**: Can edit multiple properties simultaneously side by side
3. **JSON Editor**:
- Monospace font for code editing
- Tab key support for indentation
- Syntax validation with error messages
- Live preview of changes
4. **Smooth Transitions**: Animated opening/closing with 0.3s ease
5. **Error Handling**: Invalid JSON shows clear error messages that disappear on typing
6. **Close All Button**: Single button to close all open editors at once
### Technical Implementation (Updated)
- **State Management**: Changed from single editor to array of editors with unique IDs
- **Editor Structure**: Each editor instance contains:
- `id`: Unique identifier (`propertyName-timestamp`)
- `name`: Property name
- `value`: Original value
- `element`: Reference to the element
- `editorValue`: Current JSON string
- `editorError`: Validation error message
- **Event System**: Uses custom 'editorStateChanged' event to communicate with parent dashboard
- **Dynamic Styling**: wcc-frame's bottom position changes from 100px to 400px when any 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. Click additional "Edit" buttons to open more properties side by side
4. Each editor can be saved/cancelled independently
5. "Close All" button dismisses all editors at once
6. Frame automatically resizes back when all editors are closed
### Layout Details
- **Container**: Flexbox with horizontal scrolling when multiple editors overflow
- **Editor Width**: Min 300px, max 500px, flexible between
- **Scrollbar**: Custom styled thin scrollbar for horizontal overflow
- **Header Bar**: Fixed top bar with "Property Editors" title and "Close All" button
## Properties Panel Element Detection Issue (Fixed) ## Properties Panel Element Detection Issue (Fixed)
### Problem ### Problem

2
test/pages/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './page1.js';
export * from './pageLongScroll.js';

3
test/pages/page1.ts Normal file
View File

@ -0,0 +1,3 @@
import { html } from '@design.estate/dees-element';
export const page1 = () => html` <test-demoelement></test-demoelement> `;

View File

@ -0,0 +1,138 @@
import { html } from '@design.estate/dees-element';
export const pageLongScroll = () => html`
<style>
.long-scroll-container {
padding: 40px;
max-width: 800px;
margin: 0 auto;
}
.section {
margin-bottom: 60px;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
}
h1 {
font-size: 2.5em;
margin-bottom: 20px;
color: #fff;
}
h2 {
font-size: 1.8em;
margin-bottom: 15px;
color: #ddd;
}
p {
line-height: 1.6;
color: #ccc;
margin-bottom: 15px;
}
.placeholder-content {
height: 300px;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #888;
font-size: 1.2em;
margin: 20px 0;
}
</style>
<div class="long-scroll-container">
<h1>Long Scroll Test Page</h1>
<div class="section">
<h2>Section 1: Introduction</h2>
<p>This is a long page designed to test scroll position preservation. Scroll down to see more content and then reload the page to verify that the scroll position is restored correctly.</p>
<p>The URL should update with scroll position parameters as you scroll, and when you reload the page, it should automatically scroll to the last position.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 1</span>
</div>
<div class="section">
<h2>Section 2: Testing Scroll Behavior</h2>
<p>As you scroll through this page, the dashboard should track your scroll position and update the URL accordingly. The updates should be debounced to avoid excessive URL changes.</p>
<p>Try scrolling quickly and slowly to see how the debouncing works. The URL should update smoothly without interfering with your scrolling experience.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 2</span>
</div>
<div class="section">
<h2>Section 3: Reload Testing</h2>
<p>Once you've scrolled to this section, try reloading the page. The page should automatically scroll back to this position after the content loads.</p>
<p>This demonstrates that the scroll position is being preserved across page reloads using URL parameters.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 3</span>
</div>
<div class="section">
<h2>Section 4: Navigation Testing</h2>
<p>Try navigating to a different element or page in the sidebar, then use the browser's back button to return here. The scroll position should be preserved.</p>
<p>This tests that the browser history correctly maintains scroll state for each navigation entry.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 4</span>
</div>
<div class="section">
<h2>Section 5: Deep Scroll Testing</h2>
<p>Keep scrolling! This page has plenty of content to ensure we can test scroll positions at various depths.</p>
<p>The scroll tracking should work reliably regardless of how far down the page you scroll.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 5</span>
</div>
<div class="section">
<h2>Section 6: Performance Testing</h2>
<p>Even with continuous scroll tracking, the page should remain responsive and smooth. The debouncing mechanism ensures that URL updates don't impact scrolling performance.</p>
<p>Try scrolling rapidly up and down to verify that the scrolling remains smooth.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 6</span>
</div>
<div class="section">
<h2>Section 7: Sidebar Scroll Testing</h2>
<p>Don't forget to test the sidebar scrolling as well! If the sidebar has enough items to scroll, its position should also be tracked and restored.</p>
<p>Both the main content and sidebar scroll positions should be preserved independently.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 7</span>
</div>
<div class="section">
<h2>Section 8: Edge Cases</h2>
<p>This section tests edge cases like scrolling to the very bottom of the page, then reloading.</p>
<p>The scroll restoration should handle these cases gracefully without any visual glitches or errors.</p>
</div>
<div class="placeholder-content">
<span>Placeholder Content Block 8</span>
</div>
<div class="section">
<h2>Section 9: Final Section</h2>
<p>You've reached the end of the scroll test page! Try reloading from here to ensure that even the bottom-most scroll positions are correctly preserved.</p>
<p>The scroll position tracking has been successfully implemented if you can reload and return to this exact position.</p>
</div>
</div>
`;

View File

@ -1,8 +1,8 @@
/** /**
* autocreated commitinfo by @pushrocks/commitinfo * autocreated commitinfo by @push.rocks/commitinfo
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-wcctools', name: '@design.estate/dees-wcctools',
version: '1.0.90', version: '1.1.0',
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.' description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
} }

View File

@ -38,6 +38,10 @@ export class WccDashboard extends DeesElement {
@property() @property()
public warning: string = null; public warning: string = null;
private frameScrollY: number = 0;
private sidebarScrollY: number = 0;
private scrollPositionsApplied: boolean = false;
@queryAsync('wcc-frame') @queryAsync('wcc-frame')
public wccFrame: Promise<WccFrame>; public wccFrame: Promise<WccFrame>;
@ -95,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>
@ -113,6 +124,12 @@ export class WccDashboard extends DeesElement {
public async firstUpdated() { public async firstUpdated() {
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools(); this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
// Set up scroll listeners after DOM is ready
setTimeout(() => {
this.setupScrollListeners();
}, 500);
this.domtools.router.on( this.domtools.router.on(
'/wcctools-route/:itemType/:itemName/:viewport/:theme', '/wcctools-route/:itemType/:itemName/:viewport/:theme',
async (routeInfo) => { async (routeInfo) => {
@ -125,6 +142,25 @@ export class WccDashboard extends DeesElement {
} else if (routeInfo.params.itemType === 'page') { } else if (routeInfo.params.itemType === 'page') {
this.selectedItem = this.pages[routeInfo.params.itemName]; this.selectedItem = this.pages[routeInfo.params.itemName];
} }
// Restore scroll positions from query parameters
if (routeInfo.queryParams) {
const frameScrollY = routeInfo.queryParams.frameScrollY;
const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
if (frameScrollY) {
this.frameScrollY = parseInt(frameScrollY);
}
if (sidebarScrollY) {
this.sidebarScrollY = parseInt(sidebarScrollY);
}
// Apply scroll positions after a short delay to ensure DOM is ready
setTimeout(() => {
this.applyScrollPositions();
}, 100);
}
const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup(); const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
this.selectedTheme === 'bright' this.selectedTheme === 'bright'
? domtoolsInstance.themeManager.goBright() ? domtoolsInstance.themeManager.goBright()
@ -136,7 +172,6 @@ export class WccDashboard extends DeesElement {
public async updated(changedPropertiesArg: Map<string, any>) { public async updated(changedPropertiesArg: Map<string, any>) {
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools(); this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
await this.domtools.router._handleRouteState(); await this.domtools.router._handleRouteState();
const storeElement = this.selectedItem;
const wccFrame: WccFrame = this.shadowRoot.querySelector('wcc-frame'); const wccFrame: WccFrame = this.shadowRoot.querySelector('wcc-frame');
if (changedPropertiesArg.has('selectedItemName')) { if (changedPropertiesArg.has('selectedItemName')) {
@ -173,8 +208,96 @@ export class WccDashboard extends DeesElement {
} }
public buildUrl() { public buildUrl() {
this.domtools.router.pushUrl( const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
`/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}` const queryParams = new URLSearchParams();
);
if (this.frameScrollY > 0) {
queryParams.set('frameScrollY', this.frameScrollY.toString());
}
if (this.sidebarScrollY > 0) {
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
}
const queryString = queryParams.toString();
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
this.domtools.router.pushUrl(fullUrl);
}
private scrollUpdateTimeout: NodeJS.Timeout;
private scrollListenersAttached: boolean = false;
public async setupScrollListeners() {
// Prevent duplicate listeners
if (this.scrollListenersAttached) {
return;
}
const wccFrame = await this.wccFrame;
const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar');
if (wccFrame) {
// The frame element itself is the scrollable container
wccFrame.addEventListener('scroll', () => {
this.frameScrollY = wccFrame.scrollTop;
this.debouncedScrollUpdate();
});
this.scrollListenersAttached = true;
}
if (wccSidebar) {
// The sidebar element itself is the scrollable container
wccSidebar.addEventListener('scroll', () => {
this.sidebarScrollY = wccSidebar.scrollTop;
this.debouncedScrollUpdate();
});
}
}
private debouncedScrollUpdate() {
clearTimeout(this.scrollUpdateTimeout);
this.scrollUpdateTimeout = setTimeout(() => {
this.updateUrlWithScrollState();
}, 300);
}
private updateUrlWithScrollState() {
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
const queryParams = new URLSearchParams();
if (this.frameScrollY > 0) {
queryParams.set('frameScrollY', this.frameScrollY.toString());
}
if (this.sidebarScrollY > 0) {
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
}
const queryString = queryParams.toString();
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
// Use replaceState to update URL without navigation
window.history.replaceState(null, '', fullUrl);
}
public async applyScrollPositions() {
// Only apply scroll positions once to avoid interfering with user scrolling
if (this.scrollPositionsApplied) {
return;
}
const wccFrame = await this.wccFrame;
const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar');
if (wccFrame && this.frameScrollY > 0) {
// The frame element itself is the scrollable container
wccFrame.scrollTop = this.frameScrollY;
}
if (wccSidebar && this.sidebarScrollY > 0) {
// The sidebar element itself is the scrollable container
wccSidebar.scrollTop = this.sidebarScrollY;
}
this.scrollPositionsApplied = true;
} }
} }

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,31 +34,65 @@ export class WccProperties extends DeesElement {
@state() @state()
propertyContent: TemplateResult[] = []; propertyContent: TemplateResult[] = [];
@state()
editingProperties: Array<{
id: string;
name: string;
value: any;
element: HTMLElement;
editorValue: string;
editorError: string;
}> = [];
public editorHeight: number = 300;
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`
<style> <style>
:host { :host {
font-family: 'Roboto', sans-serif; /* CSS Variables - Always dark theme */
--background: #0a0a0a;
--foreground: #e5e5e5;
--card: #0f0f0f;
--card-foreground: #f0f0f0;
--muted: #1a1a1a;
--muted-foreground: #666;
--accent: #222;
--accent-foreground: #fff;
--border: rgba(255, 255, 255, 0.06);
--input: #141414;
--primary: #3b82f6;
--primary-foreground: #fff;
--ring: #3b82f6;
--radius: 4px;
--radius-sm: 2px;
--radius-md: 4px;
--radius-lg: 6px;
/* Base styles */
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
left: 200px; left: 200px;
height: 100px; height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px;
bottom: 0px; bottom: 0px;
right: 0px; right: 0px;
overflow: hidden; overflow: hidden;
background: #111; background: var(--background);
color: #fff; color: var(--foreground);
} }
.grid { .grid {
display: grid; display: grid;
grid-template-columns: auto 150px 300px 70px; grid-template-columns: 1fr 150px 300px 70px;
height: 100%;
} }
.properties { .properties {
border-right: 1px solid #999; background: transparent;
height: 100px;
overflow-y: auto; overflow-y: auto;
display: grid; display: grid;
grid-template-columns: 33% 33% 33%; grid-template-columns: repeat(3, 1fr);
border-right: 1px solid var(--border);
align-content: start;
} }
.material-symbols-outlined { .material-symbols-outlined {
@ -77,152 +111,543 @@ export class WccProperties extends DeesElement {
} }
.properties .property { .properties .property {
padding: 5px; padding: 0.4rem;
background: #444; background: transparent;
border: 1px solid #000; border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
transition: all 0.15s ease;
} }
.properties input, .properties .property:hover {
background: rgba(255, 255, 255, 0.02);
}
.properties .property-label {
font-size: 0.65rem;
font-weight: 400;
color: #888;
margin-bottom: 0.2rem;
text-transform: capitalize;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.properties input[type="text"],
.properties input[type="number"],
.properties select { .properties select {
display: block; display: block;
width: 100%; width: 100%;
background: #333; padding: 0.25rem 0.4rem;
border: none; background: var(--input);
color: #fff; border: 1px solid transparent;
color: var(--foreground);
border-radius: var(--radius-sm);
font-size: 0.7rem;
transition: all 0.15s ease;
outline: none;
}
.properties input[type="text"]:focus,
.properties input[type="number"]:focus,
.properties select:focus {
border-color: var(--primary);
background: rgba(59, 130, 246, 0.1);
}
.properties input[type="checkbox"] {
width: 1rem;
height: 1rem;
cursor: pointer;
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;
border-right: 1px solid #999; background: transparent;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
} }
.selectorButtons2 { .selectorButtons2 {
display: grid; display: grid;
grid-template-columns: 50% 50%; grid-template-columns: 1fr 1fr;
flex: 1;
} }
.selectorButtons4 { .selectorButtons4 {
display: grid; display: grid;
grid-template-columns: 25% 25% 25% 25%; grid-template-columns: repeat(4, 1fr);
flex: 1;
} }
.button { .button {
padding: 10px; display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.5rem 0.25rem;
text-align: center; text-align: center;
border: 1px solid #000; background: transparent;
transition: all 0.2s; border: 1px solid var(--border);
transition: all 0.15s ease;
cursor: pointer;
font-size: 0.65rem;
gap: 0.2rem;
color: #999;
} }
.button:hover { .button:hover {
color: #333; background: rgba(59, 130, 246, 0.05);
background: #fff; color: #bbb;
} }
.button.selected { .button.selected {
background: #455a64; background: rgba(59, 130, 246, 0.15);
color: var(--primary);
border-color: rgba(59, 130, 246, 0.3);
} }
.button.selected:hover { .button.selected:hover {
color: #ffffff; background: rgba(59, 130, 246, 0.2);
background: #455a64; }
.button .material-symbols-outlined {
font-size: 18px;
font-variation-settings: 'FILL' 0, 'wght' 300;
}
.button.selected .material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 400;
} }
.panelheading { .panelheading {
padding: 5px; padding: 0.3rem 0.5rem;
font-weight: bold; font-weight: 500;
background: #444; font-size: 0.65rem;
border: 1px solid #000; background: rgba(59, 130, 246, 0.03);
border-bottom: 1px solid var(--border);
color: #888;
text-transform: uppercase;
letter-spacing: 0.08em;
} }
.docs { .docs {
text-align: center; display: flex;
line-height: 100px; align-items: center;
justify-content: center;
text-transform: uppercase; text-transform: uppercase;
background: transparent;
cursor: pointer;
font-size: 0.65rem;
font-weight: 500;
letter-spacing: 0.08em;
transition: all 0.15s ease;
color: #666;
} }
.docs:hover { .docs:hover {
color: #333; background: rgba(59, 130, 246, 0.05);
background: #fff; color: #999;
} }
.warning { .warning {
position: absolute; position: absolute;
background: #222; background: rgba(20, 20, 20, 0.8);
color: #CCC; color: #888;
top: 0px; top: 0.5rem;
bottom: 0px; bottom: 0.5rem;
left: 0px; left: 0.5rem;
right: 520px; right: calc(520px + 0.5rem);
text-align: center; display: flex;
padding: 35px; align-items: center;
font-size: 25px; justify-content: center;
padding: 1.5rem;
font-size: 0.85rem;
border-radius: var(--radius-md);
border: 1px solid var(--border);
backdrop-filter: blur(8px);
}
.advanced-editor-container {
position: absolute;
left: 0;
right: 0;
top: 0;
height: ${this.editorHeight}px;
background: #050505;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.editor-header-bar {
padding: 0.5rem 0.75rem;
background: transparent;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
height: 36px;
}
.editor-header-title {
font-size: 0.7rem;
font-weight: 500;
color: #666;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.editor-close-all {
padding: 0.25rem 0.5rem;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
color: #999;
font-size: 0.65rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.editor-close-all:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.12);
color: #f87171;
}
.editors-container {
flex: 1;
display: flex;
overflow-x: auto;
overflow-y: hidden;
gap: 0;
background: rgba(255, 255, 255, 0.02);
padding: 0.75rem;
}
.editors-container::-webkit-scrollbar {
height: 8px;
}
.editors-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.02);
border-radius: 4px;
}
.editors-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.08);
border-radius: 4px;
}
.editors-container::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.12);
}
.editor-instance {
min-width: 320px;
flex: 1;
max-width: 480px;
background: rgba(10, 10, 10, 0.6);
display: flex;
flex-direction: column;
border-radius: var(--radius);
overflow: hidden;
margin-right: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.2s ease;
}
.editor-instance:hover {
border-color: rgba(255, 255, 255, 0.1);
}
.editor-instance:last-child {
margin-right: 0;
}
.editor-header {
padding: 0.5rem 0.75rem;
background: rgba(255, 255, 255, 0.02);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
height: 36px;
}
.editor-title {
font-size: 0.75rem;
font-weight: 500;
color: #999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: 'Consolas', 'Monaco', monospace;
}
.editor-actions {
display: flex;
gap: 0.25rem;
}
.editor-button {
width: 24px;
height: 24px;
padding: 0;
background: transparent;
border: none;
color: #666;
font-size: 1rem;
cursor: pointer;
transition: all 0.15s ease;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
}
.editor-button:hover {
background: rgba(255, 255, 255, 0.05);
color: #999;
}
.editor-button.primary {
color: #4ade80;
}
.editor-button.primary:hover {
background: rgba(74, 222, 128, 0.1);
}
.editor-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
position: relative;
}
.editor-textarea {
width: 100%;
height: 100%;
background: transparent;
border: none;
color: #d0d0d0;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.8125rem;
line-height: 1.6;
padding: 0.75rem;
resize: none;
outline: none;
transition: all 0.15s ease;
overflow: auto;
}
.editor-textarea:focus {
background: rgba(255, 255, 255, 0.01);
}
.editor-textarea::selection {
background: rgba(59, 130, 246, 0.3);
}
.editor-error {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem 0.75rem;
background: rgba(239, 68, 68, 0.9);
backdrop-filter: blur(4px);
color: #fff;
font-size: 0.7rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.375rem;
border-top: 1px solid rgba(239, 68, 68, 0.5);
}
.editor-error::before {
content: '!';
display: inline-flex;
width: 16px;
height: 16px;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
font-size: 0.65rem;
font-weight: bold;
}
.main-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 100px;
} }
</style> </style>
<div class="grid"> ${this.editingProperties.length > 0 ? html`
<div class="properties"> <div class="advanced-editor-container">
<div class="panelheading">Properties</div> <div class="editor-header-bar">
${this.propertyContent} <div class="editor-header-title">Property Editors</div>
</div> <button class="editor-close-all" @click=${this.closeAllEditors}>
<div class="themeSelector"> Close All
<div class="panelheading">Theme</div> </button>
<div class="selectorButtons2"> </div>
<div <div class="editors-container">
class="button ${this.selectedTheme === 'dark' ? 'selected' : null}" ${this.editingProperties.length === 0 ? html`
@click=${() => { <div style="
this.selectTheme('dark'); flex: 1;
}} display: flex;
> align-items: center;
Dark<br /><i class="material-symbols-outlined">brightness_3</i> justify-content: center;
</div> color: #666;
<div font-size: 0.875rem;
class="button ${this.selectedTheme === 'bright' ? 'selected' : null}" text-align: center;
@click=${() => { padding: 2rem;
this.selectTheme('bright'); ">
}} <div>
> <div style="margin-bottom: 0.5rem; font-size: 1.5rem; opacity: 0.5;">{ }</div>
Bright<br /><i class="material-symbols-outlined">flare</i> <div>No properties being edited</div>
</div> <div style="font-size: 0.75rem; margin-top: 0.25rem; opacity: 0.7;">Click "Edit Object/Array" buttons to start editing</div>
</div>
</div>
` : null}
${this.editingProperties.map(prop => html`
<div class="editor-instance">
<div class="editor-header">
<div class="editor-title">${prop.name}</div>
<div class="editor-actions">
<button class="editor-button" @click=${() => this.handleEditorCancel(prop.id)}>✕</button>
<button class="editor-button primary" @click=${() => this.handleEditorSave(prop.id)}>✓</button>
</div>
</div>
<div class="editor-content">
<textarea
class="editor-textarea"
.value=${prop.editorValue}
@input=${(e: InputEvent) => {
const editor = this.editingProperties.find(p => p.id === prop.id);
if (editor) {
editor.editorValue = (e.target as HTMLTextAreaElement).value;
editor.editorError = '';
this.requestUpdate();
}
}}
@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>
${prop.editorError ? html`
<div class="editor-error">${prop.editorError}</div>
` : null}
</div>
</div>
`)}
</div> </div>
</div> </div>
<div class="viewportSelector"> ` : null}
<div class="panelheading">Viewport</div> <div class="main-content">
<div class="selectorButtons4"> <div class="grid">
<div <div class="properties">
class="button ${this.selectedViewport === 'phone' ? 'selected' : null}" ${this.propertyContent}
@click=${() => { </div>
this.selectViewport('phone'); <div class="themeSelector">
}} <div class="panelheading">Theme</div>
> <div class="selectorButtons2">
Phone<br /><i class="material-symbols-outlined">smartphone</i> <div
</div> class="button ${this.selectedTheme === 'dark' ? 'selected' : null}"
<div @click=${() => {
class="button ${this.selectedViewport === 'phablet' ? 'selected' : null}" this.selectTheme('dark');
@click=${() => { }}
this.selectViewport('phablet'); >
}} Dark<i class="material-symbols-outlined">brightness_3</i>
> </div>
Phablet<br /><i class="material-symbols-outlined">smartphone</i> <div
</div> class="button ${this.selectedTheme === 'bright' ? 'selected' : null}"
<div @click=${() => {
class="button ${this.selectedViewport === 'tablet' ? 'selected' : null}" this.selectTheme('bright');
@click=${() => { }}
this.selectViewport('tablet'); >
}} Bright<i class="material-symbols-outlined">flare</i>
> </div>
Tablet<br /><i class="material-symbols-outlined">tablet</i>
</div>
<div
class="button ${this.selectedViewport === 'desktop' ||
this.selectedViewport === 'native'
? 'selected'
: null}"
@click=${() => {
this.selectViewport('native');
}}
>
Desktop<br /><i class="material-symbols-outlined">desktop_windows</i>
</div> </div>
</div> </div>
<div class="viewportSelector">
<div class="panelheading">Viewport</div>
<div class="selectorButtons4">
<div
class="button ${this.selectedViewport === 'phone' ? 'selected' : null}"
@click=${() => {
this.selectViewport('phone');
}}
>
Phone<i class="material-symbols-outlined">smartphone</i>
</div>
<div
class="button ${this.selectedViewport === 'phablet' ? 'selected' : null}"
@click=${() => {
this.selectViewport('phablet');
}}
>
Phablet<i class="material-symbols-outlined">smartphone</i>
</div>
<div
class="button ${this.selectedViewport === 'tablet' ? 'selected' : null}"
@click=${() => {
this.selectViewport('tablet');
}}
>
Tablet<i class="material-symbols-outlined">tablet</i>
</div>
<div
class="button ${this.selectedViewport === 'desktop' ||
this.selectedViewport === 'native'
? 'selected'
: null}"
@click=${() => {
this.selectViewport('native');
}}
>
Desktop<i class="material-symbols-outlined">desktop_windows</i>
</div>
</div>
</div>
<div class="docs">Docs</div>
</div> </div>
<div class="docs">Docs</div> ${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
</div> </div>
${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
`; `;
} }
@ -356,7 +781,7 @@ export class WccProperties extends DeesElement {
propertyArray.push( propertyArray.push(
html` html`
<div class="property"> <div class="property">
${key} / ${propertyTypeString}<br /> <div class="property-label">${key} (${propertyTypeString})</div>
${(() => { ${(() => {
switch (propertyTypeString) { switch (propertyTypeString) {
case 'Boolean': case 'Boolean':
@ -401,6 +826,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>
@ -452,4 +888,93 @@ export class WccProperties extends DeesElement {
); );
this.dashboardRef.buildUrl(); this.dashboardRef.buildUrl();
} }
private openAdvancedEditor(propertyName: string, value: any, element: HTMLElement) {
// Check if this property is already being edited
const existingEditor = this.editingProperties.find(p => p.name === propertyName && p.element === element);
if (existingEditor) {
return; // Property is already open for editing
}
const newEditor = {
id: `${propertyName}-${Date.now()}`,
name: propertyName,
value: value,
element: element,
editorValue: JSON.stringify(value, null, 2),
editorError: ''
};
this.editingProperties = [...this.editingProperties, newEditor];
// Notify parent to resize frame if this is the first editor
if (this.editingProperties.length === 1) {
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: true },
bubbles: true
})
);
}
}
private handleEditorSave(editorId: string) {
const editor = this.editingProperties.find(p => p.id === editorId);
if (!editor) return;
try {
const parsedValue = JSON.parse(editor.editorValue);
editor.element[editor.name] = parsedValue;
// Remove this editor from the list
this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
// If no more editors, notify parent to resize frame
if (this.editingProperties.length === 0) {
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
// Refresh properties display
this.createProperties();
} catch (error) {
// Update error for this specific editor
const editorIndex = this.editingProperties.findIndex(p => p.id === editorId);
if (editorIndex !== -1) {
this.editingProperties[editorIndex].editorError = `Invalid JSON: ${error.message}`;
this.requestUpdate();
}
}
}
private handleEditorCancel(editorId: string) {
// Remove this editor from the list
this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
// If no more editors, notify parent to resize frame
if (this.editingProperties.length === 0) {
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
}
private closeAllEditors() {
this.editingProperties = [];
// Notify parent to resize frame back
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
} }

View File

@ -17,31 +17,68 @@ export class WccSidebar extends DeesElement {
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
<style> <style>
:host { :host {
/* CSS Variables - Always dark theme to match wcc-properties */
--background: #0a0a0a;
--foreground: #e5e5e5;
--card: #0f0f0f;
--card-foreground: #f0f0f0;
--muted: #1a1a1a;
--muted-foreground: #666;
--accent: #222;
--accent-foreground: #fff;
--border: rgba(255, 255, 255, 0.06);
--input: #141414;
--primary: #3b82f6;
--primary-foreground: #fff;
--ring: #3b82f6;
--radius: 4px;
display: block; display: block;
border-right: 1px solid #999; border-right: 1px solid rgba(255, 255, 255, 0.08);
font-family: 'Roboto', 'Inter', sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: 12px; font-size: 14px;
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
left: 0px; left: 0px;
width: 200px; width: 200px;
top: 0px; top: 0px;
bottom: 0px; bottom: 0px;
overflow-y: scroll; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
background: #222; background: var(--background);
color: #fff; color: var(--foreground);
padding: 5px; }
.menu {
padding: 0.5rem 0;
}
h3 {
padding: 0.3rem 0.75rem;
font-size: 0.65rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #888;
margin: 0;
margin-top: 0.5rem;
background: rgba(59, 130, 246, 0.03);
border-bottom: 1px solid var(--border);
border-top: 1px solid var(--border);
}
h3:first-child {
margin-top: 0;
} }
.material-symbols-outlined { .material-symbols-outlined {
font-family: 'Material Symbols Outlined'; font-family: 'Material Symbols Outlined';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-size: 24px; /* Preferred icon size */ font-size: 16px;
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
text-transform: none; text-transform: none;
@ -49,51 +86,75 @@ export class WccSidebar extends DeesElement {
word-wrap: normal; word-wrap: normal;
white-space: nowrap; white-space: nowrap;
direction: ltr; direction: ltr;
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48; font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
opacity: 0.5;
} }
.selectOption { .selectOption {
user-select: none; user-select: none;
position: relative; position: relative;
line-height: 24px; margin: 0.125rem 0.5rem;
padding: 5px; padding: 0.5rem 0.75rem;
transition: all 0.2s; transition: all 0.15s ease;
display: grid; display: grid;
grid-template-columns: 28px auto; grid-template-columns: 20px 1fr;
align-items: center;
gap: 0.5rem;
border-radius: var(--radius);
cursor: pointer;
font-size: 0.75rem;
color: #999;
background: transparent;
} }
.selectOption:hover { .selectOption:hover {
padding: 5px; background: rgba(59, 130, 246, 0.05);
color: #333; color: #bbb;
background: #fff; }
.selectOption:hover .material-symbols-outlined {
opacity: 0.7;
} }
.selectOption.selected { .selectOption.selected {
background: #455A64;; background: rgba(59, 130, 246, 0.15);
} color: var(--primary);
.selectOption.selected:hover {
color: #ffffff;
background: #455A64;
}
.selectOption .material-symbols-outlined {
color: #666;
display: block;
transition: all 0.2s;
} }
.selectOption.selected .material-symbols-outlined { .selectOption.selected .material-symbols-outlined {
color: #000; opacity: 1;
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.selectOption.selected:hover {
background: rgba(59, 130, 246, 0.2);
color: var(--primary);
} }
.selectOption .text { .selectOption .text {
display: block; display: block;
word-wrap: break-word; overflow: hidden;
word-break: break-all; text-overflow: ellipsis;
max-width: 100%; white-space: nowrap;
font-weight: 400;
} }
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}
</style> </style>
<div class="menu"> <div class="menu">
<h3>Pages</h3> <h3>Pages</h3>

1
ts_web/pages/index.ts Normal file
View File

@ -0,0 +1 @@
export const page1 = null;