feat(wcctools): Add section-based configuration API for setupWccTools, new Views, and section-aware routing/sidebar
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import * as plugins from '../wcctools.plugins.js';
|
||||
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
||||
import { WccDashboard } from './wcc-dashboard.js';
|
||||
import { WccDashboard, getSectionItems } from './wcc-dashboard.js';
|
||||
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
||||
|
||||
export type TElementType = 'element' | 'page';
|
||||
import type { IWccSection, TElementType } from '../wcctools.interfaces.js';
|
||||
|
||||
@customElement('wcc-sidebar')
|
||||
export class WccSidebar extends DeesElement {
|
||||
@@ -24,6 +23,12 @@ export class WccSidebar extends DeesElement {
|
||||
@state()
|
||||
accessor expandedElements: Set<string> = new Set();
|
||||
|
||||
// Track which sections are collapsed
|
||||
@state()
|
||||
accessor collapsedSections: Set<string> = new Set();
|
||||
|
||||
private sectionsInitialized = false;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<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" />
|
||||
@@ -65,7 +70,7 @@ export class WccSidebar extends DeesElement {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
.section-header {
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
@@ -77,12 +82,45 @@ export class WccSidebar extends DeesElement {
|
||||
background: rgba(59, 130, 246, 0.03);
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
h3:first-child {
|
||||
.section-header:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.section-header:hover {
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
.section-header .expand-icon {
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.section-header.collapsed .expand-icon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.section-header .section-icon {
|
||||
font-size: 14px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-content.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-weight: normal;
|
||||
@@ -216,88 +254,144 @@ export class WccSidebar extends DeesElement {
|
||||
}
|
||||
</style>
|
||||
<div class="menu">
|
||||
<h3>Pages</h3>
|
||||
${(() => {
|
||||
const pages = Object.keys(this.dashboardRef.pages);
|
||||
return pages.map(pageName => {
|
||||
const item = this.dashboardRef.pages[pageName];
|
||||
return html`
|
||||
<div
|
||||
class="selectOption ${this.selectedItem === item ? 'selected' : null}"
|
||||
@click=${async () => {
|
||||
const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('page', pageName, item, 0);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">insert_drive_file</i>
|
||||
<div class="text">${pageName}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
})()}
|
||||
<h3>Elements</h3>
|
||||
${(() => {
|
||||
const elements = Object.keys(this.dashboardRef.elements);
|
||||
return elements.map(elementName => {
|
||||
const item = this.dashboardRef.elements[elementName] as any;
|
||||
const demoCount = item.demo ? getDemoCount(item.demo) : 0;
|
||||
const isMultiDemo = item.demo && hasMultipleDemos(item.demo);
|
||||
const isExpanded = this.expandedElements.has(elementName);
|
||||
const isSelected = this.selectedItem === item;
|
||||
|
||||
if (isMultiDemo) {
|
||||
// Multi-demo element - render as expandable folder
|
||||
return html`
|
||||
<div
|
||||
class="selectOption folder ${isExpanded ? 'expanded' : ''} ${isSelected ? 'selected' : ''}"
|
||||
@click=${() => this.toggleExpanded(elementName)}
|
||||
>
|
||||
<i class="material-symbols-outlined expand-icon">chevron_right</i>
|
||||
<i class="material-symbols-outlined">folder</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
${isExpanded ? html`
|
||||
<div class="demo-children">
|
||||
${Array.from({ length: demoCount }, (_, i) => {
|
||||
const demoIndex = i;
|
||||
const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex;
|
||||
return html`
|
||||
<div
|
||||
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item, demoIndex);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">play_circle</i>
|
||||
<div class="text">demo${demoIndex + 1}</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
` : null}
|
||||
`;
|
||||
} else {
|
||||
// Single demo element - render as normal
|
||||
return html`
|
||||
<div
|
||||
class="selectOption ${isSelected ? 'selected' : null}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item, 0);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">featured_video</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
})()}
|
||||
${this.renderSections()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize collapsed sections from section config
|
||||
*/
|
||||
private initCollapsedSections() {
|
||||
if (this.sectionsInitialized) return;
|
||||
|
||||
const collapsed = new Set<string>();
|
||||
for (const section of this.dashboardRef.sections) {
|
||||
if (section.collapsed) {
|
||||
collapsed.add(section.name);
|
||||
}
|
||||
}
|
||||
this.collapsedSections = collapsed;
|
||||
this.sectionsInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render all sections
|
||||
*/
|
||||
private renderSections() {
|
||||
this.initCollapsedSections();
|
||||
|
||||
return this.dashboardRef.sections.map((section, index) => {
|
||||
const isCollapsed = this.collapsedSections.has(section.name);
|
||||
const sectionIcon = section.icon || (section.type === 'pages' ? 'insert_drive_file' : 'widgets');
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="section-header ${isCollapsed ? 'collapsed' : ''}"
|
||||
@click=${() => this.toggleSectionCollapsed(section.name)}
|
||||
>
|
||||
<i class="material-symbols-outlined expand-icon">expand_more</i>
|
||||
${section.icon ? html`<i class="material-symbols-outlined section-icon">${section.icon}</i>` : null}
|
||||
<span>${section.name}</span>
|
||||
</div>
|
||||
<div class="section-content ${isCollapsed ? 'collapsed' : ''}">
|
||||
${this.renderSectionItems(section)}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render items for a section
|
||||
*/
|
||||
private renderSectionItems(section: IWccSection) {
|
||||
const entries = getSectionItems(section);
|
||||
|
||||
if (section.type === 'pages') {
|
||||
return entries.map(([pageName, item]) => {
|
||||
return html`
|
||||
<div
|
||||
class="selectOption ${this.selectedItem === item ? 'selected' : ''}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('page', pageName, item, 0, section);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">insert_drive_file</i>
|
||||
<div class="text">${pageName}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
// type === 'elements'
|
||||
return entries.map(([elementName, item]) => {
|
||||
const anonItem = item as any;
|
||||
const demoCount = anonItem.demo ? getDemoCount(anonItem.demo) : 0;
|
||||
const isMultiDemo = anonItem.demo && hasMultipleDemos(anonItem.demo);
|
||||
const isExpanded = this.expandedElements.has(elementName);
|
||||
const isSelected = this.selectedItem === item;
|
||||
|
||||
if (isMultiDemo) {
|
||||
// Multi-demo element - render as expandable folder
|
||||
return html`
|
||||
<div
|
||||
class="selectOption folder ${isExpanded ? 'expanded' : ''} ${isSelected ? 'selected' : ''}"
|
||||
@click=${() => this.toggleExpanded(elementName)}
|
||||
>
|
||||
<i class="material-symbols-outlined expand-icon">chevron_right</i>
|
||||
<i class="material-symbols-outlined">folder</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
${isExpanded ? html`
|
||||
<div class="demo-children">
|
||||
${Array.from({ length: demoCount }, (_, i) => {
|
||||
const demoIndex = i;
|
||||
const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex;
|
||||
return html`
|
||||
<div
|
||||
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item, demoIndex, section);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">play_circle</i>
|
||||
<div class="text">demo${demoIndex + 1}</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
` : null}
|
||||
`;
|
||||
} else {
|
||||
// Single demo element
|
||||
return html`
|
||||
<div
|
||||
class="selectOption ${isSelected ? 'selected' : ''}"
|
||||
@click=${async () => {
|
||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||
this.selectItem('element', elementName, item, 0, section);
|
||||
}}
|
||||
>
|
||||
<i class="material-symbols-outlined">featured_video</i>
|
||||
<div class="text">${elementName}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private toggleSectionCollapsed(sectionName: string) {
|
||||
const newSet = new Set(this.collapsedSections);
|
||||
if (newSet.has(sectionName)) {
|
||||
newSet.delete(sectionName);
|
||||
} else {
|
||||
newSet.add(sectionName);
|
||||
}
|
||||
this.collapsedSections = newSet;
|
||||
}
|
||||
|
||||
private toggleExpanded(elementName: string) {
|
||||
const newSet = new Set(this.expandedElements);
|
||||
if (newSet.has(elementName)) {
|
||||
@@ -313,30 +407,50 @@ export class WccSidebar extends DeesElement {
|
||||
|
||||
// Auto-expand folder when a multi-demo element is selected
|
||||
if (changedProperties.has('selectedItem') && this.selectedItem) {
|
||||
const elementName = Object.keys(this.dashboardRef.elements).find(
|
||||
name => this.dashboardRef.elements[name] === this.selectedItem
|
||||
);
|
||||
if (elementName) {
|
||||
const item = this.dashboardRef.elements[elementName] as any;
|
||||
if (item.demo && hasMultipleDemos(item.demo)) {
|
||||
if (!this.expandedElements.has(elementName)) {
|
||||
const newSet = new Set(this.expandedElements);
|
||||
newSet.add(elementName);
|
||||
this.expandedElements = newSet;
|
||||
// Find the element in any section
|
||||
for (const section of this.dashboardRef.sections) {
|
||||
if (section.type !== 'elements') continue;
|
||||
|
||||
const entries = getSectionItems(section);
|
||||
const found = entries.find(([_, item]) => item === this.selectedItem);
|
||||
if (found) {
|
||||
const [elementName, item] = found;
|
||||
const anonItem = item as any;
|
||||
if (anonItem.demo && hasMultipleDemos(anonItem.demo)) {
|
||||
if (!this.expandedElements.has(elementName)) {
|
||||
const newSet = new Set(this.expandedElements);
|
||||
newSet.add(elementName);
|
||||
this.expandedElements = newSet;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement, demoIndex: number = 0) {
|
||||
public selectItem(
|
||||
typeArg: TElementType,
|
||||
itemNameArg: string,
|
||||
itemArg: TTemplateFactory | DeesElement,
|
||||
demoIndex: number = 0,
|
||||
section?: IWccSection
|
||||
) {
|
||||
console.log('selected item');
|
||||
console.log(itemNameArg);
|
||||
console.log(itemArg);
|
||||
console.log('demo index:', demoIndex);
|
||||
console.log('section:', section?.name);
|
||||
|
||||
this.selectedItem = itemArg;
|
||||
this.selectedType = typeArg;
|
||||
this.dashboardRef.selectedDemoIndex = demoIndex;
|
||||
|
||||
// Set the selected section on dashboard
|
||||
if (section) {
|
||||
this.dashboardRef.selectedSection = section;
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('selectedType', {
|
||||
detail: typeArg
|
||||
@@ -356,7 +470,6 @@ export class WccSidebar extends DeesElement {
|
||||
this.dashboardRef.buildUrl();
|
||||
|
||||
// Force re-render to update demo child selection indicator
|
||||
// (needed when switching between demos of the same element)
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user