@design.estate/dees-wcctools 3.4.0 → 3.5.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 +637 -233
- package/dist_bundle/bundle.js.map +4 -4
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/wcc-contextmenu.d.ts +25 -0
- package/dist_ts_web/elements/wcc-contextmenu.js +257 -0
- package/dist_ts_web/elements/wcc-dashboard.d.ts +1 -0
- package/dist_ts_web/elements/wcc-dashboard.js +54 -2
- package/dist_ts_web/elements/wcc-sidebar.d.ts +13 -0
- package/dist_ts_web/elements/wcc-sidebar.js +257 -62
- package/dist_watch/bundle.js +1046 -385
- package/dist_watch/bundle.js.map +4 -4
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/wcc-contextmenu.ts +211 -0
- package/ts_web/elements/wcc-dashboard.ts +45 -0
- package/ts_web/elements/wcc-sidebar.ts +269 -60
|
@@ -4,6 +4,7 @@ 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
6
|
import type { IWccSection, TElementType } from '../wcctools.interfaces.js';
|
|
7
|
+
import { WccContextmenu } from './wcc-contextmenu.js';
|
|
7
8
|
|
|
8
9
|
@customElement('wcc-sidebar')
|
|
9
10
|
export class WccSidebar extends DeesElement {
|
|
@@ -31,6 +32,10 @@ export class WccSidebar extends DeesElement {
|
|
|
31
32
|
@property()
|
|
32
33
|
accessor searchQuery: string = '';
|
|
33
34
|
|
|
35
|
+
// Pinned items as Set of "sectionName::itemName"
|
|
36
|
+
@property({ attribute: false })
|
|
37
|
+
accessor pinnedItems: Set<string> = new Set();
|
|
38
|
+
|
|
34
39
|
private sectionsInitialized = false;
|
|
35
40
|
|
|
36
41
|
public render(): TemplateResult {
|
|
@@ -159,7 +164,7 @@ export class WccSidebar extends DeesElement {
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
.selectOption.folder {
|
|
162
|
-
grid-template-columns: 16px
|
|
167
|
+
grid-template-columns: 16px 1fr;
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
.selectOption .expand-icon {
|
|
@@ -288,6 +293,65 @@ export class WccSidebar extends DeesElement {
|
|
|
288
293
|
background: rgba(59, 130, 246, 0.3);
|
|
289
294
|
border-radius: 2px;
|
|
290
295
|
}
|
|
296
|
+
|
|
297
|
+
/* Pinned item highlight in original section */
|
|
298
|
+
.selectOption.pinned {
|
|
299
|
+
background: rgba(245, 158, 11, 0.08);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.selectOption.pinned:hover {
|
|
303
|
+
background: rgba(245, 158, 11, 0.12);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.selectOption.pinned.selected {
|
|
307
|
+
background: rgba(245, 158, 11, 0.18);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* Pinned section styling */
|
|
311
|
+
.section-header.pinned-section {
|
|
312
|
+
background: rgba(245, 158, 11, 0.08);
|
|
313
|
+
color: #f59e0b;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.section-header.pinned-section:hover {
|
|
317
|
+
background: rgba(245, 158, 11, 0.12);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.section-header.pinned-section .section-icon {
|
|
321
|
+
opacity: 0.8;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Section tag for pinned items */
|
|
325
|
+
.section-tag {
|
|
326
|
+
font-size: 0.55rem;
|
|
327
|
+
color: #555;
|
|
328
|
+
margin-left: auto;
|
|
329
|
+
text-transform: uppercase;
|
|
330
|
+
letter-spacing: 0.03em;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Group container */
|
|
334
|
+
.item-group {
|
|
335
|
+
margin: 0.375rem 0.375rem;
|
|
336
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
337
|
+
border-radius: 6px;
|
|
338
|
+
padding: 0.25rem 0;
|
|
339
|
+
background: rgba(255, 255, 255, 0.01);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.item-group-legend {
|
|
343
|
+
font-size: 0.55rem;
|
|
344
|
+
text-transform: uppercase;
|
|
345
|
+
letter-spacing: 0.05em;
|
|
346
|
+
color: #555;
|
|
347
|
+
padding: 0.125rem 0.625rem 0.25rem;
|
|
348
|
+
display: block;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.item-group .selectOption {
|
|
352
|
+
margin-left: 0.25rem;
|
|
353
|
+
margin-right: 0.25rem;
|
|
354
|
+
}
|
|
291
355
|
</style>
|
|
292
356
|
<div class="search-container">
|
|
293
357
|
<input
|
|
@@ -299,6 +363,7 @@ export class WccSidebar extends DeesElement {
|
|
|
299
363
|
/>
|
|
300
364
|
</div>
|
|
301
365
|
<div class="menu">
|
|
366
|
+
${this.renderPinnedSection()}
|
|
302
367
|
${this.renderSections()}
|
|
303
368
|
</div>
|
|
304
369
|
`;
|
|
@@ -308,7 +373,7 @@ export class WccSidebar extends DeesElement {
|
|
|
308
373
|
* Initialize collapsed sections from section config
|
|
309
374
|
*/
|
|
310
375
|
private initCollapsedSections() {
|
|
311
|
-
if (this.sectionsInitialized) return;
|
|
376
|
+
if (this.sectionsInitialized || !this.dashboardRef?.sections) return;
|
|
312
377
|
|
|
313
378
|
const collapsed = new Set<string>();
|
|
314
379
|
for (const section of this.dashboardRef.sections) {
|
|
@@ -320,13 +385,116 @@ export class WccSidebar extends DeesElement {
|
|
|
320
385
|
this.sectionsInitialized = true;
|
|
321
386
|
}
|
|
322
387
|
|
|
388
|
+
// ============ Pinning helpers ============
|
|
389
|
+
|
|
390
|
+
private getPinKey(sectionName: string, itemName: string): string {
|
|
391
|
+
return `${sectionName}::${itemName}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private isPinned(sectionName: string, itemName: string): boolean {
|
|
395
|
+
return this.pinnedItems.has(this.getPinKey(sectionName, itemName));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private togglePin(sectionName: string, itemName: string) {
|
|
399
|
+
const key = this.getPinKey(sectionName, itemName);
|
|
400
|
+
const newPinned = new Set(this.pinnedItems);
|
|
401
|
+
if (newPinned.has(key)) {
|
|
402
|
+
newPinned.delete(key);
|
|
403
|
+
} else {
|
|
404
|
+
newPinned.add(key);
|
|
405
|
+
}
|
|
406
|
+
this.pinnedItems = newPinned;
|
|
407
|
+
this.dispatchEvent(new CustomEvent('pinnedChanged', { detail: newPinned }));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private showContextMenu(e: MouseEvent, sectionName: string, itemName: string) {
|
|
411
|
+
const isPinned = this.isPinned(sectionName, itemName);
|
|
412
|
+
WccContextmenu.show(e, [
|
|
413
|
+
{
|
|
414
|
+
name: isPinned ? 'Unpin' : 'Pin',
|
|
415
|
+
iconName: isPinned ? 'push_pin' : 'push_pin',
|
|
416
|
+
action: () => this.togglePin(sectionName, itemName),
|
|
417
|
+
},
|
|
418
|
+
]);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Render the PINNED section (only if there are pinned items)
|
|
423
|
+
*/
|
|
424
|
+
private renderPinnedSection() {
|
|
425
|
+
if (!this.dashboardRef?.sections || this.pinnedItems.size === 0) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const isCollapsed = this.collapsedSections.has('__pinned__');
|
|
430
|
+
|
|
431
|
+
// Collect pinned items with their original section info
|
|
432
|
+
const pinnedEntries: Array<{ sectionName: string; itemName: string; item: any; section: IWccSection }> = [];
|
|
433
|
+
|
|
434
|
+
for (const key of this.pinnedItems) {
|
|
435
|
+
const [sectionName, itemName] = key.split('::');
|
|
436
|
+
const section = this.dashboardRef.sections.find(s => s.name === sectionName);
|
|
437
|
+
if (section) {
|
|
438
|
+
const entries = getSectionItems(section);
|
|
439
|
+
const found = entries.find(([name]) => name === itemName);
|
|
440
|
+
if (found) {
|
|
441
|
+
pinnedEntries.push({ sectionName, itemName, item: found[1], section });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Filter by search
|
|
447
|
+
const filteredEntries = pinnedEntries.filter(e => this.matchesSearch(e.itemName));
|
|
448
|
+
|
|
449
|
+
if (filteredEntries.length === 0 && this.searchQuery) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return html`
|
|
454
|
+
<div
|
|
455
|
+
class="section-header pinned-section ${isCollapsed ? 'collapsed' : ''}"
|
|
456
|
+
@click=${() => this.toggleSectionCollapsed('__pinned__')}
|
|
457
|
+
>
|
|
458
|
+
<i class="material-symbols-outlined expand-icon">expand_more</i>
|
|
459
|
+
<i class="material-symbols-outlined section-icon">push_pin</i>
|
|
460
|
+
<span>Pinned</span>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="section-content ${isCollapsed ? 'collapsed' : ''}">
|
|
463
|
+
${filteredEntries.map(({ sectionName, itemName, item, section }) => {
|
|
464
|
+
const isSelected = this.selectedItem === item;
|
|
465
|
+
const type = section.type === 'elements' ? 'element' : 'page';
|
|
466
|
+
const icon = section.type === 'elements' ? 'featured_video' : 'insert_drive_file';
|
|
467
|
+
|
|
468
|
+
return html`
|
|
469
|
+
<div
|
|
470
|
+
class="selectOption ${isSelected ? 'selected' : ''}"
|
|
471
|
+
@click=${async () => {
|
|
472
|
+
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
473
|
+
this.selectItem(type, itemName, item, 0, section);
|
|
474
|
+
}}
|
|
475
|
+
@contextmenu=${(e: MouseEvent) => this.showContextMenu(e, sectionName, itemName)}
|
|
476
|
+
>
|
|
477
|
+
<i class="material-symbols-outlined">${icon}</i>
|
|
478
|
+
<div class="text">${this.highlightMatch(itemName)}</div>
|
|
479
|
+
<span class="section-tag">${sectionName}</span>
|
|
480
|
+
</div>
|
|
481
|
+
`;
|
|
482
|
+
})}
|
|
483
|
+
</div>
|
|
484
|
+
`;
|
|
485
|
+
}
|
|
486
|
+
|
|
323
487
|
/**
|
|
324
488
|
* Render all sections
|
|
325
489
|
*/
|
|
326
490
|
private renderSections() {
|
|
491
|
+
if (!this.dashboardRef?.sections) {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
|
|
327
495
|
this.initCollapsedSections();
|
|
328
496
|
|
|
329
|
-
return this.dashboardRef.sections.map((section
|
|
497
|
+
return this.dashboardRef.sections.map((section) => {
|
|
330
498
|
// Check if section has any matching items
|
|
331
499
|
const entries = getSectionItems(section);
|
|
332
500
|
const filteredEntries = entries.filter(([name]) => this.matchesSearch(name));
|
|
@@ -365,13 +533,15 @@ export class WccSidebar extends DeesElement {
|
|
|
365
533
|
|
|
366
534
|
if (section.type === 'pages') {
|
|
367
535
|
return filteredEntries.map(([pageName, item]) => {
|
|
536
|
+
const isPinned = this.isPinned(section.name, pageName);
|
|
368
537
|
return html`
|
|
369
538
|
<div
|
|
370
|
-
class="selectOption ${this.selectedItem === item ? 'selected' : ''}"
|
|
539
|
+
class="selectOption ${this.selectedItem === item ? 'selected' : ''} ${isPinned ? 'pinned' : ''}"
|
|
371
540
|
@click=${async () => {
|
|
372
541
|
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
373
542
|
this.selectItem('page', pageName, item, 0, section);
|
|
374
543
|
}}
|
|
544
|
+
@contextmenu=${(e: MouseEvent) => this.showContextMenu(e, section.name, pageName)}
|
|
375
545
|
>
|
|
376
546
|
<i class="material-symbols-outlined">insert_drive_file</i>
|
|
377
547
|
<div class="text">${this.highlightMatch(pageName)}</div>
|
|
@@ -379,62 +549,101 @@ export class WccSidebar extends DeesElement {
|
|
|
379
549
|
`;
|
|
380
550
|
});
|
|
381
551
|
} else {
|
|
382
|
-
// type === 'elements'
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
|
|
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
|
-
`;
|
|
552
|
+
// type === 'elements' - group by demoGroup
|
|
553
|
+
const groupedItems = new Map<string | null, Array<[string, any]>>();
|
|
554
|
+
|
|
555
|
+
for (const entry of filteredEntries) {
|
|
556
|
+
const [, item] = entry;
|
|
557
|
+
const group = (item as any).demoGroup || null;
|
|
558
|
+
if (!groupedItems.has(group)) {
|
|
559
|
+
groupedItems.set(group, []);
|
|
436
560
|
}
|
|
437
|
-
|
|
561
|
+
groupedItems.get(group)!.push(entry);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const result: TemplateResult[] = [];
|
|
565
|
+
|
|
566
|
+
// Render ungrouped items first
|
|
567
|
+
const ungrouped = groupedItems.get(null) || [];
|
|
568
|
+
for (const entry of ungrouped) {
|
|
569
|
+
result.push(this.renderElementItem(entry, section));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Render grouped items
|
|
573
|
+
for (const [groupName, items] of groupedItems) {
|
|
574
|
+
if (groupName === null) continue;
|
|
575
|
+
|
|
576
|
+
result.push(html`
|
|
577
|
+
<div class="item-group">
|
|
578
|
+
<span class="item-group-legend">${groupName}</span>
|
|
579
|
+
${items.map((entry) => this.renderElementItem(entry, section))}
|
|
580
|
+
</div>
|
|
581
|
+
`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return result;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Render a single element item (used by renderSectionItems)
|
|
590
|
+
*/
|
|
591
|
+
private renderElementItem(entry: [string, any], section: IWccSection): TemplateResult {
|
|
592
|
+
const [elementName, item] = entry;
|
|
593
|
+
const anonItem = item as any;
|
|
594
|
+
const demoCount = anonItem.demo ? getDemoCount(anonItem.demo) : 0;
|
|
595
|
+
const isMultiDemo = anonItem.demo && hasMultipleDemos(anonItem.demo);
|
|
596
|
+
const isExpanded = this.expandedElements.has(elementName);
|
|
597
|
+
const isSelected = this.selectedItem === item;
|
|
598
|
+
const isPinned = this.isPinned(section.name, elementName);
|
|
599
|
+
|
|
600
|
+
if (isMultiDemo) {
|
|
601
|
+
// Multi-demo element - render as expandable folder
|
|
602
|
+
return html`
|
|
603
|
+
<div
|
|
604
|
+
class="selectOption folder ${isExpanded ? 'expanded' : ''} ${isSelected ? 'selected' : ''} ${isPinned ? 'pinned' : ''}"
|
|
605
|
+
@click=${() => this.toggleExpanded(elementName)}
|
|
606
|
+
@contextmenu=${(e: MouseEvent) => this.showContextMenu(e, section.name, elementName)}
|
|
607
|
+
>
|
|
608
|
+
<i class="material-symbols-outlined expand-icon">chevron_right</i>
|
|
609
|
+
<div class="text">${this.highlightMatch(elementName)}</div>
|
|
610
|
+
</div>
|
|
611
|
+
${isExpanded ? html`
|
|
612
|
+
<div class="demo-children">
|
|
613
|
+
${Array.from({ length: demoCount }, (_, i) => {
|
|
614
|
+
const demoIndex = i;
|
|
615
|
+
const isThisDemoSelected = isSelected && this.dashboardRef.selectedDemoIndex === demoIndex;
|
|
616
|
+
return html`
|
|
617
|
+
<div
|
|
618
|
+
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
|
619
|
+
@click=${async () => {
|
|
620
|
+
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
621
|
+
this.selectItem('element', elementName, item, demoIndex, section);
|
|
622
|
+
}}
|
|
623
|
+
>
|
|
624
|
+
<i class="material-symbols-outlined">play_circle</i>
|
|
625
|
+
<div class="text">demo${demoIndex + 1}</div>
|
|
626
|
+
</div>
|
|
627
|
+
`;
|
|
628
|
+
})}
|
|
629
|
+
</div>
|
|
630
|
+
` : null}
|
|
631
|
+
`;
|
|
632
|
+
} else {
|
|
633
|
+
// Single demo element
|
|
634
|
+
return html`
|
|
635
|
+
<div
|
|
636
|
+
class="selectOption ${isSelected ? 'selected' : ''} ${isPinned ? 'pinned' : ''}"
|
|
637
|
+
@click=${async () => {
|
|
638
|
+
await plugins.deesDomtools.DomTools.setupDomTools();
|
|
639
|
+
this.selectItem('element', elementName, item, 0, section);
|
|
640
|
+
}}
|
|
641
|
+
@contextmenu=${(e: MouseEvent) => this.showContextMenu(e, section.name, elementName)}
|
|
642
|
+
>
|
|
643
|
+
<i class="material-symbols-outlined">featured_video</i>
|
|
644
|
+
<div class="text">${this.highlightMatch(elementName)}</div>
|
|
645
|
+
</div>
|
|
646
|
+
`;
|
|
438
647
|
}
|
|
439
648
|
}
|
|
440
649
|
|
|
@@ -485,7 +694,7 @@ export class WccSidebar extends DeesElement {
|
|
|
485
694
|
super.updated(changedProperties);
|
|
486
695
|
|
|
487
696
|
// Auto-expand folder when a multi-demo element is selected
|
|
488
|
-
if (changedProperties.has('selectedItem') && this.selectedItem) {
|
|
697
|
+
if (changedProperties.has('selectedItem') && this.selectedItem && this.dashboardRef?.sections) {
|
|
489
698
|
// Find the element in any section
|
|
490
699
|
for (const section of this.dashboardRef.sections) {
|
|
491
700
|
if (section.type !== 'elements') continue;
|