@design.estate/dees-wcctools 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_bundle/bundle.js +387 -113
- package/dist_bundle/bundle.js.map +3 -3
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/wcc-dashboard.d.ts +20 -8
- package/dist_ts_web/elements/wcc-dashboard.js +151 -45
- package/dist_ts_web/elements/wcc-sidebar.d.ts +21 -2
- package/dist_ts_web/elements/wcc-sidebar.js +261 -77
- package/dist_ts_web/index.d.ts +21 -1
- package/dist_ts_web/index.js +55 -3
- package/dist_ts_web/wcctools.interfaces.d.ts +29 -0
- package/dist_ts_web/wcctools.interfaces.js +2 -0
- package/dist_watch/bundle.js +1470 -164
- package/dist_watch/bundle.js.map +4 -4
- package/package.json +1 -1
- package/readme.hints.md +62 -0
- package/readme.md +158 -73
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/wcc-dashboard.ts +139 -37
- package/ts_web/elements/wcc-sidebar.ts +287 -95
- package/ts_web/index.ts +67 -2
- package/ts_web/readme.md +55 -5
- package/ts_web/wcctools.interfaces.ts +31 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as plugins from '../wcctools.plugins.js';
|
|
2
2
|
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
|
3
|
-
import { WccDashboard } from './wcc-dashboard.js';
|
|
3
|
+
import { WccDashboard, getSectionItems } from './wcc-dashboard.js';
|
|
4
4
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
|
5
5
|
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
|
6
|
-
|
|
7
|
-
export type TElementType = 'element' | 'page';
|
|
6
|
+
import type { IWccSection, TElementType } from '../wcctools.interfaces.js';
|
|
8
7
|
|
|
9
8
|
@customElement('wcc-sidebar')
|
|
10
9
|
export class WccSidebar extends DeesElement {
|
|
@@ -24,6 +23,16 @@ export class WccSidebar extends DeesElement {
|
|
|
24
23
|
@state()
|
|
25
24
|
accessor expandedElements: Set<string> = new Set();
|
|
26
25
|
|
|
26
|
+
// Track which sections are collapsed
|
|
27
|
+
@state()
|
|
28
|
+
accessor collapsedSections: Set<string> = new Set();
|
|
29
|
+
|
|
30
|
+
// Search query for filtering sidebar items
|
|
31
|
+
@property()
|
|
32
|
+
accessor searchQuery: string = '';
|
|
33
|
+
|
|
34
|
+
private sectionsInitialized = false;
|
|
35
|
+
|
|
27
36
|
public render(): TemplateResult {
|
|
28
37
|
return html`
|
|
29
38
|
<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 +74,7 @@ export class WccSidebar extends DeesElement {
|
|
|
65
74
|
padding: 0.5rem 0;
|
|
66
75
|
}
|
|
67
76
|
|
|
68
|
-
|
|
77
|
+
.section-header {
|
|
69
78
|
padding: 0.3rem 0.75rem;
|
|
70
79
|
font-size: 0.65rem;
|
|
71
80
|
font-weight: 500;
|
|
@@ -77,12 +86,45 @@ export class WccSidebar extends DeesElement {
|
|
|
77
86
|
background: rgba(59, 130, 246, 0.03);
|
|
78
87
|
border-bottom: 1px solid var(--border);
|
|
79
88
|
border-top: 1px solid var(--border);
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 0.5rem;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
user-select: none;
|
|
94
|
+
transition: all 0.15s ease;
|
|
80
95
|
}
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
.section-header:first-child {
|
|
83
98
|
margin-top: 0;
|
|
84
99
|
}
|
|
85
100
|
|
|
101
|
+
.section-header:hover {
|
|
102
|
+
background: rgba(59, 130, 246, 0.08);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.section-header .expand-icon {
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
transition: transform 0.2s ease;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.section-header.collapsed .expand-icon {
|
|
112
|
+
transform: rotate(-90deg);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.section-header .section-icon {
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
opacity: 0.6;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.section-content {
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.section-content.collapsed {
|
|
125
|
+
display: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
86
128
|
.material-symbols-outlined {
|
|
87
129
|
font-family: 'Material Symbols Outlined';
|
|
88
130
|
font-weight: normal;
|
|
@@ -214,90 +256,198 @@ export class WccSidebar extends DeesElement {
|
|
|
214
256
|
::-webkit-scrollbar-thumb:hover {
|
|
215
257
|
background: rgba(255, 255, 255, 0.2);
|
|
216
258
|
}
|
|
259
|
+
|
|
260
|
+
.search-container {
|
|
261
|
+
padding: 0.5rem;
|
|
262
|
+
border-bottom: 1px solid var(--border);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.search-input {
|
|
266
|
+
width: 100%;
|
|
267
|
+
box-sizing: border-box;
|
|
268
|
+
background: var(--input);
|
|
269
|
+
border: 1px solid var(--border);
|
|
270
|
+
border-radius: var(--radius);
|
|
271
|
+
padding: 0.5rem 0.75rem;
|
|
272
|
+
color: var(--foreground);
|
|
273
|
+
font-size: 0.75rem;
|
|
274
|
+
font-family: inherit;
|
|
275
|
+
outline: none;
|
|
276
|
+
transition: border-color 0.15s ease;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.search-input:focus {
|
|
280
|
+
border-color: var(--primary);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.search-input::placeholder {
|
|
284
|
+
color: var(--muted-foreground);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.highlight {
|
|
288
|
+
background: rgba(59, 130, 246, 0.3);
|
|
289
|
+
border-radius: 2px;
|
|
290
|
+
}
|
|
217
291
|
</style>
|
|
292
|
+
<div class="search-container">
|
|
293
|
+
<input
|
|
294
|
+
type="text"
|
|
295
|
+
class="search-input"
|
|
296
|
+
placeholder="Search..."
|
|
297
|
+
.value=${this.searchQuery}
|
|
298
|
+
@input=${this.handleSearchInput}
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
218
301
|
<div class="menu">
|
|
219
|
-
|
|
220
|
-
${(() => {
|
|
221
|
-
const pages = Object.keys(this.dashboardRef.pages);
|
|
222
|
-
return pages.map(pageName => {
|
|
223
|
-
const item = this.dashboardRef.pages[pageName];
|
|
224
|
-
return html`
|
|
225
|
-
<div
|
|
226
|
-
class="selectOption ${this.selectedItem === item ? 'selected' : null}"
|
|
227
|
-
@click=${async () => {
|
|
228
|
-
const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
|
229
|
-
this.selectItem('page', pageName, item, 0);
|
|
230
|
-
}}
|
|
231
|
-
>
|
|
232
|
-
<i class="material-symbols-outlined">insert_drive_file</i>
|
|
233
|
-
<div class="text">${pageName}</div>
|
|
234
|
-
</div>
|
|
235
|
-
`;
|
|
236
|
-
});
|
|
237
|
-
})()}
|
|
238
|
-
<h3>Elements</h3>
|
|
239
|
-
${(() => {
|
|
240
|
-
const elements = Object.keys(this.dashboardRef.elements);
|
|
241
|
-
return elements.map(elementName => {
|
|
242
|
-
const item = this.dashboardRef.elements[elementName] as any;
|
|
243
|
-
const demoCount = item.demo ? getDemoCount(item.demo) : 0;
|
|
244
|
-
const isMultiDemo = item.demo && hasMultipleDemos(item.demo);
|
|
245
|
-
const isExpanded = this.expandedElements.has(elementName);
|
|
246
|
-
const isSelected = this.selectedItem === item;
|
|
247
|
-
|
|
248
|
-
if (isMultiDemo) {
|
|
249
|
-
// Multi-demo element - render as expandable folder
|
|
250
|
-
return html`
|
|
251
|
-
<div
|
|
252
|
-
class="selectOption folder ${isExpanded ? 'expanded' : ''} ${isSelected ? 'selected' : ''}"
|
|
253
|
-
@click=${() => this.toggleExpanded(elementName)}
|
|
254
|
-
>
|
|
255
|
-
<i class="material-symbols-outlined expand-icon">chevron_right</i>
|
|
256
|
-
<i class="material-symbols-outlined">folder</i>
|
|
257
|
-
<div class="text">${elementName}</div>
|
|
258
|
-
</div>
|
|
259
|
-
${isExpanded ? html`
|
|
260
|
-
<div class="demo-children">
|
|
261
|
-
${Array.from({ length: demoCount }, (_, i) => {
|
|
262
|
-
const demoIndex = i;
|
|
263
|
-
const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex;
|
|
264
|
-
return html`
|
|
265
|
-
<div
|
|
266
|
-
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
|
267
|
-
@click=${async () => {
|
|
268
|
-
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
269
|
-
this.selectItem('element', elementName, item, demoIndex);
|
|
270
|
-
}}
|
|
271
|
-
>
|
|
272
|
-
<i class="material-symbols-outlined">play_circle</i>
|
|
273
|
-
<div class="text">demo${demoIndex + 1}</div>
|
|
274
|
-
</div>
|
|
275
|
-
`;
|
|
276
|
-
})}
|
|
277
|
-
</div>
|
|
278
|
-
` : null}
|
|
279
|
-
`;
|
|
280
|
-
} else {
|
|
281
|
-
// Single demo element - render as normal
|
|
282
|
-
return html`
|
|
283
|
-
<div
|
|
284
|
-
class="selectOption ${isSelected ? 'selected' : null}"
|
|
285
|
-
@click=${async () => {
|
|
286
|
-
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
287
|
-
this.selectItem('element', elementName, item, 0);
|
|
288
|
-
}}
|
|
289
|
-
>
|
|
290
|
-
<i class="material-symbols-outlined">featured_video</i>
|
|
291
|
-
<div class="text">${elementName}</div>
|
|
292
|
-
</div>
|
|
293
|
-
`;
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
})()}
|
|
302
|
+
${this.renderSections()}
|
|
297
303
|
</div>
|
|
298
304
|
`;
|
|
299
305
|
}
|
|
300
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Initialize collapsed sections from section config
|
|
309
|
+
*/
|
|
310
|
+
private initCollapsedSections() {
|
|
311
|
+
if (this.sectionsInitialized) return;
|
|
312
|
+
|
|
313
|
+
const collapsed = new Set<string>();
|
|
314
|
+
for (const section of this.dashboardRef.sections) {
|
|
315
|
+
if (section.collapsed) {
|
|
316
|
+
collapsed.add(section.name);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
this.collapsedSections = collapsed;
|
|
320
|
+
this.sectionsInitialized = true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Render all sections
|
|
325
|
+
*/
|
|
326
|
+
private renderSections() {
|
|
327
|
+
this.initCollapsedSections();
|
|
328
|
+
|
|
329
|
+
return this.dashboardRef.sections.map((section, index) => {
|
|
330
|
+
// Check if section has any matching items
|
|
331
|
+
const entries = getSectionItems(section);
|
|
332
|
+
const filteredEntries = entries.filter(([name]) => this.matchesSearch(name));
|
|
333
|
+
|
|
334
|
+
// Hide section if no items match the search
|
|
335
|
+
if (filteredEntries.length === 0 && this.searchQuery) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const isCollapsed = this.collapsedSections.has(section.name);
|
|
340
|
+
const sectionIcon = section.icon || (section.type === 'pages' ? 'insert_drive_file' : 'widgets');
|
|
341
|
+
|
|
342
|
+
return html`
|
|
343
|
+
<div
|
|
344
|
+
class="section-header ${isCollapsed ? 'collapsed' : ''}"
|
|
345
|
+
@click=${() => this.toggleSectionCollapsed(section.name)}
|
|
346
|
+
>
|
|
347
|
+
<i class="material-symbols-outlined expand-icon">expand_more</i>
|
|
348
|
+
${section.icon ? html`<i class="material-symbols-outlined section-icon">${section.icon}</i>` : null}
|
|
349
|
+
<span>${section.name}</span>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="section-content ${isCollapsed ? 'collapsed' : ''}">
|
|
352
|
+
${this.renderSectionItems(section)}
|
|
353
|
+
</div>
|
|
354
|
+
`;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Render items for a section
|
|
360
|
+
*/
|
|
361
|
+
private renderSectionItems(section: IWccSection) {
|
|
362
|
+
const entries = getSectionItems(section);
|
|
363
|
+
// Filter entries by search query
|
|
364
|
+
const filteredEntries = entries.filter(([name]) => this.matchesSearch(name));
|
|
365
|
+
|
|
366
|
+
if (section.type === 'pages') {
|
|
367
|
+
return filteredEntries.map(([pageName, item]) => {
|
|
368
|
+
return html`
|
|
369
|
+
<div
|
|
370
|
+
class="selectOption ${this.selectedItem === item ? 'selected' : ''}"
|
|
371
|
+
@click=${async () => {
|
|
372
|
+
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
373
|
+
this.selectItem('page', pageName, item, 0, section);
|
|
374
|
+
}}
|
|
375
|
+
>
|
|
376
|
+
<i class="material-symbols-outlined">insert_drive_file</i>
|
|
377
|
+
<div class="text">${this.highlightMatch(pageName)}</div>
|
|
378
|
+
</div>
|
|
379
|
+
`;
|
|
380
|
+
});
|
|
381
|
+
} else {
|
|
382
|
+
// type === 'elements'
|
|
383
|
+
return filteredEntries.map(([elementName, item]) => {
|
|
384
|
+
const anonItem = item as any;
|
|
385
|
+
const demoCount = anonItem.demo ? getDemoCount(anonItem.demo) : 0;
|
|
386
|
+
const isMultiDemo = anonItem.demo && hasMultipleDemos(anonItem.demo);
|
|
387
|
+
const isExpanded = this.expandedElements.has(elementName);
|
|
388
|
+
const isSelected = this.selectedItem === item;
|
|
389
|
+
|
|
390
|
+
if (isMultiDemo) {
|
|
391
|
+
// Multi-demo element - render as expandable folder
|
|
392
|
+
return html`
|
|
393
|
+
<div
|
|
394
|
+
class="selectOption folder ${isExpanded ? 'expanded' : ''} ${isSelected ? 'selected' : ''}"
|
|
395
|
+
@click=${() => this.toggleExpanded(elementName)}
|
|
396
|
+
>
|
|
397
|
+
<i class="material-symbols-outlined expand-icon">chevron_right</i>
|
|
398
|
+
<i class="material-symbols-outlined">folder</i>
|
|
399
|
+
<div class="text">${this.highlightMatch(elementName)}</div>
|
|
400
|
+
</div>
|
|
401
|
+
${isExpanded ? html`
|
|
402
|
+
<div class="demo-children">
|
|
403
|
+
${Array.from({ length: demoCount }, (_, i) => {
|
|
404
|
+
const demoIndex = i;
|
|
405
|
+
const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex;
|
|
406
|
+
return html`
|
|
407
|
+
<div
|
|
408
|
+
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
|
409
|
+
@click=${async () => {
|
|
410
|
+
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
411
|
+
this.selectItem('element', elementName, item, demoIndex, section);
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
<i class="material-symbols-outlined">play_circle</i>
|
|
415
|
+
<div class="text">demo${demoIndex + 1}</div>
|
|
416
|
+
</div>
|
|
417
|
+
`;
|
|
418
|
+
})}
|
|
419
|
+
</div>
|
|
420
|
+
` : null}
|
|
421
|
+
`;
|
|
422
|
+
} else {
|
|
423
|
+
// Single demo element
|
|
424
|
+
return html`
|
|
425
|
+
<div
|
|
426
|
+
class="selectOption ${isSelected ? 'selected' : ''}"
|
|
427
|
+
@click=${async () => {
|
|
428
|
+
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
429
|
+
this.selectItem('element', elementName, item, 0, section);
|
|
430
|
+
}}
|
|
431
|
+
>
|
|
432
|
+
<i class="material-symbols-outlined">featured_video</i>
|
|
433
|
+
<div class="text">${this.highlightMatch(elementName)}</div>
|
|
434
|
+
</div>
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private toggleSectionCollapsed(sectionName: string) {
|
|
442
|
+
const newSet = new Set(this.collapsedSections);
|
|
443
|
+
if (newSet.has(sectionName)) {
|
|
444
|
+
newSet.delete(sectionName);
|
|
445
|
+
} else {
|
|
446
|
+
newSet.add(sectionName);
|
|
447
|
+
}
|
|
448
|
+
this.collapsedSections = newSet;
|
|
449
|
+
}
|
|
450
|
+
|
|
301
451
|
private toggleExpanded(elementName: string) {
|
|
302
452
|
const newSet = new Set(this.expandedElements);
|
|
303
453
|
if (newSet.has(elementName)) {
|
|
@@ -308,35 +458,78 @@ export class WccSidebar extends DeesElement {
|
|
|
308
458
|
this.expandedElements = newSet;
|
|
309
459
|
}
|
|
310
460
|
|
|
461
|
+
private handleSearchInput(e: Event) {
|
|
462
|
+
const input = e.target as HTMLInputElement;
|
|
463
|
+
this.searchQuery = input.value;
|
|
464
|
+
this.dispatchEvent(new CustomEvent('searchChanged', { detail: this.searchQuery }));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private matchesSearch(name: string): boolean {
|
|
468
|
+
if (!this.searchQuery) return true;
|
|
469
|
+
return name.toLowerCase().includes(this.searchQuery.toLowerCase());
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private highlightMatch(text: string): TemplateResult {
|
|
473
|
+
if (!this.searchQuery) return html`${text}`;
|
|
474
|
+
const lowerText = text.toLowerCase();
|
|
475
|
+
const lowerQuery = this.searchQuery.toLowerCase();
|
|
476
|
+
const index = lowerText.indexOf(lowerQuery);
|
|
477
|
+
if (index === -1) return html`${text}`;
|
|
478
|
+
const before = text.slice(0, index);
|
|
479
|
+
const match = text.slice(index, index + this.searchQuery.length);
|
|
480
|
+
const after = text.slice(index + this.searchQuery.length);
|
|
481
|
+
return html`${before}<span class="highlight">${match}</span>${after}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
311
484
|
protected updated(changedProperties: Map<string, unknown>) {
|
|
312
485
|
super.updated(changedProperties);
|
|
313
486
|
|
|
314
487
|
// Auto-expand folder when a multi-demo element is selected
|
|
315
488
|
if (changedProperties.has('selectedItem') && this.selectedItem) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
489
|
+
// Find the element in any section
|
|
490
|
+
for (const section of this.dashboardRef.sections) {
|
|
491
|
+
if (section.type !== 'elements') continue;
|
|
492
|
+
|
|
493
|
+
const entries = getSectionItems(section);
|
|
494
|
+
const found = entries.find(([_, item]) => item === this.selectedItem);
|
|
495
|
+
if (found) {
|
|
496
|
+
const [elementName, item] = found;
|
|
497
|
+
const anonItem = item as any;
|
|
498
|
+
if (anonItem.demo && hasMultipleDemos(anonItem.demo)) {
|
|
499
|
+
if (!this.expandedElements.has(elementName)) {
|
|
500
|
+
const newSet = new Set(this.expandedElements);
|
|
501
|
+
newSet.add(elementName);
|
|
502
|
+
this.expandedElements = newSet;
|
|
503
|
+
}
|
|
326
504
|
}
|
|
505
|
+
break;
|
|
327
506
|
}
|
|
328
507
|
}
|
|
329
508
|
}
|
|
330
509
|
}
|
|
331
510
|
|
|
332
|
-
public selectItem(
|
|
511
|
+
public selectItem(
|
|
512
|
+
typeArg: TElementType,
|
|
513
|
+
itemNameArg: string,
|
|
514
|
+
itemArg: TTemplateFactory | DeesElement,
|
|
515
|
+
demoIndex: number = 0,
|
|
516
|
+
section?: IWccSection
|
|
517
|
+
) {
|
|
333
518
|
console.log('selected item');
|
|
334
519
|
console.log(itemNameArg);
|
|
335
520
|
console.log(itemArg);
|
|
336
521
|
console.log('demo index:', demoIndex);
|
|
522
|
+
console.log('section:', section?.name);
|
|
523
|
+
|
|
337
524
|
this.selectedItem = itemArg;
|
|
338
525
|
this.selectedType = typeArg;
|
|
339
526
|
this.dashboardRef.selectedDemoIndex = demoIndex;
|
|
527
|
+
|
|
528
|
+
// Set the selected section on dashboard
|
|
529
|
+
if (section) {
|
|
530
|
+
this.dashboardRef.selectedSection = section;
|
|
531
|
+
}
|
|
532
|
+
|
|
340
533
|
this.dispatchEvent(
|
|
341
534
|
new CustomEvent('selectedType', {
|
|
342
535
|
detail: typeArg
|
|
@@ -356,7 +549,6 @@ export class WccSidebar extends DeesElement {
|
|
|
356
549
|
this.dashboardRef.buildUrl();
|
|
357
550
|
|
|
358
551
|
// Force re-render to update demo child selection indicator
|
|
359
|
-
// (needed when switching between demos of the same element)
|
|
360
552
|
this.requestUpdate();
|
|
361
553
|
}
|
|
362
554
|
}
|
package/ts_web/index.ts
CHANGED
|
@@ -1,21 +1,86 @@
|
|
|
1
1
|
import { WccDashboard } from './elements/wcc-dashboard.js';
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
import type { TTemplateFactory } from './elements/wcctools.helpers.js';
|
|
4
|
+
import type { IWccConfig, IWccSection } from './wcctools.interfaces.js';
|
|
4
5
|
|
|
5
6
|
// Export recording components and service
|
|
6
7
|
export { RecorderService, type IRecorderEvents, type IRecordingOptions } from './services/recorder.service.js';
|
|
7
8
|
export { WccRecordButton } from './elements/wcc-record-button.js';
|
|
8
9
|
export { WccRecordingPanel } from './elements/wcc-recording-panel.js';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
// Export types for external use
|
|
12
|
+
export type { IWccConfig, IWccSection } from './wcctools.interfaces.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts legacy (elements, pages) format to new sections config
|
|
16
|
+
*/
|
|
17
|
+
const convertLegacyToConfig = (
|
|
11
18
|
elementsArg?: { [key: string]: LitElement },
|
|
12
19
|
pagesArg?: Record<string, TTemplateFactory>
|
|
20
|
+
): IWccConfig => {
|
|
21
|
+
const sections: IWccSection[] = [];
|
|
22
|
+
|
|
23
|
+
if (pagesArg && Object.keys(pagesArg).length > 0) {
|
|
24
|
+
sections.push({
|
|
25
|
+
name: 'Pages',
|
|
26
|
+
type: 'pages',
|
|
27
|
+
items: pagesArg,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (elementsArg && Object.keys(elementsArg).length > 0) {
|
|
32
|
+
sections.push({
|
|
33
|
+
name: 'Elements',
|
|
34
|
+
type: 'elements',
|
|
35
|
+
items: elementsArg,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { sections };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if the argument is the new config format
|
|
44
|
+
*/
|
|
45
|
+
const isWccConfig = (arg: any): arg is IWccConfig => {
|
|
46
|
+
return arg && typeof arg === 'object' && 'sections' in arg && Array.isArray(arg.sections);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Setup WCC Tools dashboard
|
|
51
|
+
*
|
|
52
|
+
* New format (recommended):
|
|
53
|
+
* ```typescript
|
|
54
|
+
* setupWccTools({
|
|
55
|
+
* sections: [
|
|
56
|
+
* { name: 'Elements', type: 'elements', items: elements },
|
|
57
|
+
* { name: 'Pages', type: 'pages', items: pages },
|
|
58
|
+
* ]
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* Legacy format (still supported):
|
|
63
|
+
* ```typescript
|
|
64
|
+
* setupWccTools(elements, pages);
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
const setupWccTools = (
|
|
68
|
+
configOrElements?: IWccConfig | { [key: string]: LitElement },
|
|
69
|
+
pagesArg?: Record<string, TTemplateFactory>
|
|
13
70
|
) => {
|
|
71
|
+
let config: IWccConfig;
|
|
72
|
+
|
|
73
|
+
if (isWccConfig(configOrElements)) {
|
|
74
|
+
config = configOrElements;
|
|
75
|
+
} else {
|
|
76
|
+
config = convertLegacyToConfig(configOrElements, pagesArg);
|
|
77
|
+
}
|
|
78
|
+
|
|
14
79
|
let hasRun = false;
|
|
15
80
|
const runWccToolsSetup = async () => {
|
|
16
81
|
if (document.readyState === 'complete' && !hasRun) {
|
|
17
82
|
hasRun = true;
|
|
18
|
-
const wccTools = new WccDashboard(
|
|
83
|
+
const wccTools = new WccDashboard(config);
|
|
19
84
|
document.querySelector('body').append(wccTools);
|
|
20
85
|
}
|
|
21
86
|
};
|
package/ts_web/readme.md
CHANGED
|
@@ -14,6 +14,40 @@ pnpm add -D @design.estate/dees-wcctools
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
+
### Sections-based Configuration (Recommended)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { setupWccTools } from '@design.estate/dees-wcctools';
|
|
21
|
+
import * as elements from './elements/index.js';
|
|
22
|
+
import * as views from './views/index.js';
|
|
23
|
+
import * as pages from './pages/index.js';
|
|
24
|
+
|
|
25
|
+
setupWccTools({
|
|
26
|
+
sections: [
|
|
27
|
+
{
|
|
28
|
+
name: 'Pages',
|
|
29
|
+
type: 'pages',
|
|
30
|
+
items: pages,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Views',
|
|
34
|
+
type: 'elements',
|
|
35
|
+
items: views,
|
|
36
|
+
icon: 'web',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Elements',
|
|
40
|
+
type: 'elements',
|
|
41
|
+
items: elements,
|
|
42
|
+
filter: (name) => !name.startsWith('internal-'),
|
|
43
|
+
sort: ([a], [b]) => a.localeCompare(b),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Legacy Format (Still Supported)
|
|
50
|
+
|
|
17
51
|
```typescript
|
|
18
52
|
import { setupWccTools } from '@design.estate/dees-wcctools';
|
|
19
53
|
import { MyButton } from './components/my-button.js';
|
|
@@ -30,6 +64,8 @@ setupWccTools({
|
|
|
30
64
|
| Export | Description |
|
|
31
65
|
|--------|-------------|
|
|
32
66
|
| `setupWccTools` | Initialize the component catalogue dashboard |
|
|
67
|
+
| `IWccConfig` | TypeScript interface for sections configuration |
|
|
68
|
+
| `IWccSection` | TypeScript interface for individual section |
|
|
33
69
|
|
|
34
70
|
### Recording Components
|
|
35
71
|
|
|
@@ -41,6 +77,18 @@ setupWccTools({
|
|
|
41
77
|
| `IRecorderEvents` | TypeScript interface for recorder callbacks |
|
|
42
78
|
| `IRecordingOptions` | TypeScript interface for recording options |
|
|
43
79
|
|
|
80
|
+
## Section Configuration
|
|
81
|
+
|
|
82
|
+
| Property | Type | Description |
|
|
83
|
+
|----------|------|-------------|
|
|
84
|
+
| `name` | `string` | Display name for the section header |
|
|
85
|
+
| `type` | `'elements' \| 'pages'` | Rendering behavior |
|
|
86
|
+
| `items` | `Record<string, any>` | Element classes or page factories |
|
|
87
|
+
| `filter` | `(name, item) => boolean` | Optional filter function |
|
|
88
|
+
| `sort` | `([a, itemA], [b, itemB]) => number` | Optional sort function |
|
|
89
|
+
| `icon` | `string` | Material Symbols icon name |
|
|
90
|
+
| `collapsed` | `boolean` | Start section collapsed (default: false) |
|
|
91
|
+
|
|
44
92
|
## Internal Components
|
|
45
93
|
|
|
46
94
|
The module includes these internal web components:
|
|
@@ -48,8 +96,8 @@ The module includes these internal web components:
|
|
|
48
96
|
| Component | Description |
|
|
49
97
|
|-----------|-------------|
|
|
50
98
|
| `wcc-dashboard` | Main dashboard container with routing |
|
|
51
|
-
| `wcc-sidebar` | Navigation sidebar with
|
|
52
|
-
| `wcc-frame` |
|
|
99
|
+
| `wcc-sidebar` | Navigation sidebar with collapsible sections |
|
|
100
|
+
| `wcc-frame` | Responsive viewport with size controls |
|
|
53
101
|
| `wcc-properties` | Property panel with live editing |
|
|
54
102
|
| `wcc-record-button` | Recording state indicator button |
|
|
55
103
|
| `wcc-recording-panel` | Recording workflow UI |
|
|
@@ -72,14 +120,14 @@ const events: IRecorderEvents = {
|
|
|
72
120
|
const recorder = new RecorderService(events);
|
|
73
121
|
|
|
74
122
|
// Load available microphones
|
|
75
|
-
const mics = await recorder.loadMicrophones(true);
|
|
123
|
+
const mics = await recorder.loadMicrophones(true);
|
|
76
124
|
|
|
77
125
|
// Start audio level monitoring
|
|
78
126
|
await recorder.startAudioMonitoring(mics[0].deviceId);
|
|
79
127
|
|
|
80
128
|
// Start recording
|
|
81
129
|
await recorder.startRecording({
|
|
82
|
-
mode: 'viewport',
|
|
130
|
+
mode: 'viewport',
|
|
83
131
|
audioDeviceId: mics[0].deviceId,
|
|
84
132
|
viewportElement: document.querySelector('.viewport'),
|
|
85
133
|
});
|
|
@@ -99,10 +147,11 @@ recorder.dispose();
|
|
|
99
147
|
```
|
|
100
148
|
ts_web/
|
|
101
149
|
├── index.ts # Main exports
|
|
150
|
+
├── wcctools.interfaces.ts # Type definitions
|
|
102
151
|
├── elements/
|
|
103
152
|
│ ├── wcc-dashboard.ts # Root dashboard component
|
|
104
153
|
│ ├── wcc-sidebar.ts # Navigation sidebar
|
|
105
|
-
│ ├── wcc-frame.ts # Responsive
|
|
154
|
+
│ ├── wcc-frame.ts # Responsive viewport
|
|
106
155
|
│ ├── wcc-properties.ts # Property editing panel
|
|
107
156
|
│ ├── wcc-record-button.ts # Recording button
|
|
108
157
|
│ ├── wcc-recording-panel.ts # Recording options/preview
|
|
@@ -116,6 +165,7 @@ ts_web/
|
|
|
116
165
|
## Features
|
|
117
166
|
|
|
118
167
|
- 🎨 Interactive component preview
|
|
168
|
+
- 📂 Section-based sidebar with filtering & sorting
|
|
119
169
|
- 🔧 Real-time property editing with type detection
|
|
120
170
|
- 🌓 Theme switching (light/dark)
|
|
121
171
|
- 📱 Responsive viewport testing
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for a section in the WCC Tools sidebar
|
|
3
|
+
*/
|
|
4
|
+
export interface IWccSection {
|
|
5
|
+
/** Display name for the section header */
|
|
6
|
+
name: string;
|
|
7
|
+
/** How items in this section are rendered - 'elements' show demos, 'pages' render directly */
|
|
8
|
+
type: 'elements' | 'pages';
|
|
9
|
+
/** The items in this section - either element classes or page factory functions */
|
|
10
|
+
items: Record<string, any>;
|
|
11
|
+
/** Optional filter function to include/exclude items */
|
|
12
|
+
filter?: (name: string, item: any) => boolean;
|
|
13
|
+
/** Optional sort function for ordering items */
|
|
14
|
+
sort?: (a: [string, any], b: [string, any]) => number;
|
|
15
|
+
/** Optional Material icon name for the section header */
|
|
16
|
+
icon?: string;
|
|
17
|
+
/** Whether this section should start collapsed (default: false) */
|
|
18
|
+
collapsed?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration object for setupWccTools
|
|
23
|
+
*/
|
|
24
|
+
export interface IWccConfig {
|
|
25
|
+
sections: IWccSection[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Type for element selection types - now section-based
|
|
30
|
+
*/
|
|
31
|
+
export type TElementType = 'element' | 'page';
|