From ad033c81042023deae1541126219005b415637f6 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 11 Dec 2025 15:49:04 +0000 Subject: [PATCH] update --- ts_web/elements/wcc-dashboard.ts | 94 ++++++++++++++---- ts_web/elements/wcc-sidebar.ts | 143 ++++++++++++++++++++++++---- ts_web/elements/wcctools.helpers.ts | 31 ++++++ 3 files changed, 233 insertions(+), 35 deletions(-) diff --git a/ts_web/elements/wcc-dashboard.ts b/ts_web/elements/wcc-dashboard.ts index 7dd0e18..4bd76ca 100644 --- a/ts_web/elements/wcc-dashboard.ts +++ b/ts_web/elements/wcc-dashboard.ts @@ -1,5 +1,5 @@ import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element'; -import { resolveTemplateFactory } from './wcctools.helpers.js'; +import { resolveTemplateFactory, getDemoAtIndex, getDemoCount, hasMultipleDemos } from './wcctools.helpers.js'; import type { TTemplateFactory } from './wcctools.helpers.js'; import * as plugins from '../wcctools.plugins.js'; @@ -25,6 +25,9 @@ export class WccDashboard extends DeesElement { @property() accessor selectedItem: TTemplateFactory | DeesElement; + @property({ type: Number }) + accessor selectedDemoIndex: number = 0; + @property() accessor selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop'; @@ -151,11 +154,13 @@ export class WccDashboard extends DeesElement { this.setupScrollListeners(); }, 500); + // Route with demo index (new format) this.domtools.router.on( - '/wcctools-route/:itemType/:itemName/:viewport/:theme', + '/wcctools-route/:itemType/:itemName/:demoIndex/:viewport/:theme', async (routeInfo) => { this.selectedType = routeInfo.params.itemType as TElementType; this.selectedItemName = routeInfo.params.itemName; + this.selectedDemoIndex = parseInt(routeInfo.params.demoIndex) || 0; this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport; this.selectedTheme = routeInfo.params.theme as TTheme; if (routeInfo.params.itemType === 'element') { @@ -163,25 +168,65 @@ export class WccDashboard extends DeesElement { } else if (routeInfo.params.itemType === 'page') { 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(); + this.selectedTheme === 'bright' + ? domtoolsInstance.themeManager.goBright() + : domtoolsInstance.themeManager.goDark(); + } + ); + + // Legacy route without demo index (for backwards compatibility) + this.domtools.router.on( + '/wcctools-route/:itemType/:itemName/:viewport/:theme', + async (routeInfo) => { + this.selectedType = routeInfo.params.itemType as TElementType; + this.selectedItemName = routeInfo.params.itemName; + this.selectedDemoIndex = 0; // Default to first demo + this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport; + this.selectedTheme = routeInfo.params.theme as TTheme; + if (routeInfo.params.itemType === 'element') { + this.selectedItem = this.elements[routeInfo.params.itemName]; + } else if (routeInfo.params.itemType === 'page') { + 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(); this.selectedTheme === 'bright' ? domtoolsInstance.themeManager.goBright() @@ -218,33 +263,48 @@ export class WccDashboard extends DeesElement { this.setWarning(`component ${anonItem.name} does not expose a demo property.`); return; } - if (!(typeof anonItem.demo === 'function')) { + + // Support both single demo (function) and multiple demos (array) + const isArray = Array.isArray(anonItem.demo); + const isFunction = typeof anonItem.demo === 'function'; + + if (!isArray && !isFunction) { this.setWarning( - `component ${anonItem.name} has demo property, but it is not of type function` + `component ${anonItem.name} has demo property, but it is not a function or array of functions` ); return; } + + // Get the specific demo to render + const demoFactory = getDemoAtIndex(anonItem.demo, this.selectedDemoIndex); + if (!demoFactory) { + this.setWarning( + `component ${anonItem.name} does not have a demo at index ${this.selectedDemoIndex + 1}` + ); + return; + } + this.setWarning(null); const viewport = await wccFrame.getViewportElement(); - const demoTemplate = await resolveTemplateFactory(() => anonItem.demo()); + const demoTemplate = await resolveTemplateFactory(demoFactory); render(demoTemplate, viewport); } } public buildUrl() { - const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`; + const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${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); } @@ -286,19 +346,19 @@ export class WccDashboard extends DeesElement { } private updateUrlWithScrollState() { - const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`; + const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${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); } diff --git a/ts_web/elements/wcc-sidebar.ts b/ts_web/elements/wcc-sidebar.ts index 57bfdd3..bb5a83f 100644 --- a/ts_web/elements/wcc-sidebar.ts +++ b/ts_web/elements/wcc-sidebar.ts @@ -1,7 +1,8 @@ import * as plugins from '../wcctools.plugins.js'; -import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element'; +import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element'; import { WccDashboard } from './wcc-dashboard.js'; import type { TTemplateFactory } from './wcctools.helpers.js'; +import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js'; export type TElementType = 'element' | 'page'; @@ -19,6 +20,10 @@ export class WccSidebar extends DeesElement { @property() accessor isFullscreen: boolean = false; + // Track which elements are expanded (for multi-demo elements) + @state() + accessor expandedElements: Set = new Set(); + public render(): TemplateResult { return html` @@ -110,7 +115,21 @@ export class WccSidebar extends DeesElement { color: #999; background: transparent; } - + + .selectOption.folder { + grid-template-columns: 16px 20px 1fr; + } + + .selectOption .expand-icon { + font-size: 14px; + opacity: 0.5; + transition: transform 0.2s ease; + } + + .selectOption.expanded .expand-icon { + transform: rotate(90deg); + } + .selectOption:hover { background: rgba(59, 130, 246, 0.05); color: #bbb; @@ -143,6 +162,42 @@ export class WccSidebar extends DeesElement { font-weight: 400; } + .demo-children { + margin-left: 1rem; + overflow: hidden; + } + + .demo-child { + user-select: none; + position: relative; + margin: 0.125rem 0.5rem; + padding: 0.35rem 0.75rem; + transition: all 0.15s ease; + display: grid; + grid-template-columns: 16px 1fr; + align-items: center; + gap: 0.5rem; + border-radius: var(--radius); + cursor: pointer; + font-size: 0.7rem; + color: #777; + background: transparent; + } + + .demo-child:hover { + background: rgba(59, 130, 246, 0.05); + color: #bbb; + } + + .demo-child.selected { + background: rgba(59, 130, 246, 0.15); + color: var(--primary); + } + + .demo-child .material-symbols-outlined { + font-size: 14px; + } + ::-webkit-scrollbar { width: 8px; } @@ -171,7 +226,7 @@ export class WccSidebar extends DeesElement { class="selectOption ${this.selectedItem === item ? 'selected' : null}" @click=${async () => { const domtools = await plugins.deesDomtools.DomTools.setupDomTools(); - this.selectItem('page', pageName, item); + this.selectItem('page', pageName, item, 0); }} > insert_drive_file @@ -184,31 +239,83 @@ export class WccSidebar extends DeesElement { ${(() => { const elements = Object.keys(this.dashboardRef.elements); return elements.map(elementName => { - const item = this.dashboardRef.elements[elementName]; - return html` -
{ - const domtools = await plugins.deesDomtools.DomTools.setupDomTools(); - this.selectItem('element', elementName, item); - }} - > - featured_video -
${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` +
this.toggleExpanded(elementName)} + > + chevron_right + folder +
${elementName}
+
+ ${isExpanded ? html` +
+ ${Array.from({ length: demoCount }, (_, i) => { + const demoIndex = i; + const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex; + return html` +
{ + await plugins.deesDomtools.DomTools.setupDomTools(); + this.selectItem('element', elementName, item, demoIndex); + }} + > + play_circle +
demo${demoIndex + 1}
+
+ `; + })} +
+ ` : null} + `; + } else { + // Single demo element - render as normal + return html` +
{ + await plugins.deesDomtools.DomTools.setupDomTools(); + this.selectItem('element', elementName, item, 0); + }} + > + featured_video +
${elementName}
+
+ `; + } }); })()} `; } - public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement) { + private toggleExpanded(elementName: string) { + const newSet = new Set(this.expandedElements); + if (newSet.has(elementName)) { + newSet.delete(elementName); + } else { + newSet.add(elementName); + } + this.expandedElements = newSet; + } + + public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement, demoIndex: number = 0) { console.log('selected item'); console.log(itemNameArg); console.log(itemArg); + console.log('demo index:', demoIndex); this.selectedItem = itemArg; this.selectedType = typeArg; + this.dashboardRef.selectedDemoIndex = demoIndex; this.dispatchEvent( new CustomEvent('selectedType', { detail: typeArg @@ -224,7 +331,7 @@ export class WccSidebar extends DeesElement { detail: itemArg }) ); - + this.dashboardRef.buildUrl(); } } diff --git a/ts_web/elements/wcctools.helpers.ts b/ts_web/elements/wcctools.helpers.ts index fc097ab..ab66c62 100644 --- a/ts_web/elements/wcctools.helpers.ts +++ b/ts_web/elements/wcctools.helpers.ts @@ -2,8 +2,39 @@ import type { TemplateResult } from 'lit'; export type TTemplateFactory = () => TemplateResult | Promise; +// Demo can be a single function or an array of functions +export type TDemoDefinition = TTemplateFactory | TTemplateFactory[]; + export const resolveTemplateFactory = async ( factoryArg: TTemplateFactory ): Promise => { return await Promise.resolve(factoryArg()); }; + +/** + * Get the number of demos for an element + */ +export const getDemoCount = (demo: TDemoDefinition): number => { + if (Array.isArray(demo)) { + return demo.length; + } + return 1; +}; + +/** + * Get a specific demo by index (0-based internally, displayed as 1-based) + */ +export const getDemoAtIndex = (demo: TDemoDefinition, index: number): TTemplateFactory | null => { + if (Array.isArray(demo)) { + return demo[index] ?? null; + } + // Single demo - only index 0 is valid + return index === 0 ? demo : null; +}; + +/** + * Check if an element has multiple demos + */ +export const hasMultipleDemos = (demo: TDemoDefinition): boolean => { + return Array.isArray(demo) && demo.length > 1; +};