@design.estate/dees-wcctools 1.2.1 → 2.0.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.
Files changed (42) hide show
  1. package/dist_bundle/bundle.js +1764 -218
  2. package/dist_bundle/bundle.js.map +4 -4
  3. package/dist_ts_demotools/demotools.d.ts +1 -1
  4. package/dist_ts_demotools/demotools.js +86 -38
  5. package/dist_ts_web/00_commitinfo_data.js +1 -1
  6. package/dist_ts_web/elements/wcc-dashboard.d.ts +11 -10
  7. package/dist_ts_web/elements/wcc-dashboard.js +370 -246
  8. package/dist_ts_web/elements/wcc-frame.d.ts +3 -3
  9. package/dist_ts_web/elements/wcc-frame.js +108 -57
  10. package/dist_ts_web/elements/wcc-properties.d.ts +14 -8
  11. package/dist_ts_web/elements/wcc-properties.js +442 -323
  12. package/dist_ts_web/elements/wcc-record-button.d.ts +12 -0
  13. package/dist_ts_web/elements/wcc-record-button.js +165 -0
  14. package/dist_ts_web/elements/wcc-recording-panel.d.ts +42 -0
  15. package/dist_ts_web/elements/wcc-recording-panel.js +1067 -0
  16. package/dist_ts_web/elements/wcc-sidebar.d.ts +7 -5
  17. package/dist_ts_web/elements/wcc-sidebar.js +250 -81
  18. package/dist_ts_web/elements/wcctools.helpers.d.ts +13 -0
  19. package/dist_ts_web/elements/wcctools.helpers.js +26 -1
  20. package/dist_ts_web/index.d.ts +3 -0
  21. package/dist_ts_web/index.js +5 -1
  22. package/dist_ts_web/services/ffmpeg.service.d.ts +42 -0
  23. package/dist_ts_web/services/ffmpeg.service.js +276 -0
  24. package/dist_ts_web/services/mp4.service.d.ts +32 -0
  25. package/dist_ts_web/services/mp4.service.js +139 -0
  26. package/dist_ts_web/services/recorder.service.d.ts +44 -0
  27. package/dist_ts_web/services/recorder.service.js +307 -0
  28. package/dist_watch/bundle.js +2126 -541
  29. package/dist_watch/bundle.js.map +4 -4
  30. package/package.json +8 -8
  31. package/readme.md +133 -141
  32. package/ts_web/00_commitinfo_data.ts +1 -1
  33. package/ts_web/elements/wcc-dashboard.ts +86 -26
  34. package/ts_web/elements/wcc-frame.ts +3 -3
  35. package/ts_web/elements/wcc-properties.ts +53 -9
  36. package/ts_web/elements/wcc-record-button.ts +108 -0
  37. package/ts_web/elements/wcc-recording-panel.ts +978 -0
  38. package/ts_web/elements/wcc-sidebar.ts +133 -22
  39. package/ts_web/elements/wcctools.helpers.ts +31 -0
  40. package/ts_web/index.ts +5 -0
  41. package/ts_web/readme.md +123 -0
  42. package/ts_web/services/recorder.service.ts +393 -0
@@ -1,23 +1,28 @@
1
1
  import * as plugins from '../wcctools.plugins.js';
2
- import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
2
+ import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
3
3
  import { WccDashboard } from './wcc-dashboard.js';
4
4
  import type { TTemplateFactory } from './wcctools.helpers.js';
5
+ import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
5
6
 
6
7
  export type TElementType = 'element' | 'page';
7
8
 
8
9
  @customElement('wcc-sidebar')
9
10
  export class WccSidebar extends DeesElement {
10
11
  @property({ attribute: false })
11
- public selectedItem: DeesElement | TTemplateFactory;
12
+ accessor selectedItem: DeesElement | TTemplateFactory;
12
13
 
13
14
  @property({ attribute: false })
14
- public selectedType: TElementType;
15
+ accessor selectedType: TElementType;
15
16
 
16
17
  @property()
17
- public dashboardRef: WccDashboard;
18
+ accessor dashboardRef: WccDashboard;
18
19
 
19
20
  @property()
20
- public isFullscreen: boolean = false;
21
+ accessor isFullscreen: boolean = false;
22
+
23
+ // Track which elements are expanded (for multi-demo elements)
24
+ @state()
25
+ accessor expandedElements: Set<string> = new Set();
21
26
 
22
27
  public render(): TemplateResult {
23
28
  return html`
@@ -110,7 +115,21 @@ export class WccSidebar extends DeesElement {
110
115
  color: #999;
111
116
  background: transparent;
112
117
  }
113
-
118
+
119
+ .selectOption.folder {
120
+ grid-template-columns: 16px 20px 1fr;
121
+ }
122
+
123
+ .selectOption .expand-icon {
124
+ font-size: 14px;
125
+ opacity: 0.5;
126
+ transition: transform 0.2s ease;
127
+ }
128
+
129
+ .selectOption.expanded .expand-icon {
130
+ transform: rotate(90deg);
131
+ }
132
+
114
133
  .selectOption:hover {
115
134
  background: rgba(59, 130, 246, 0.05);
116
135
  color: #bbb;
@@ -143,6 +162,42 @@ export class WccSidebar extends DeesElement {
143
162
  font-weight: 400;
144
163
  }
145
164
 
165
+ .demo-children {
166
+ margin-left: 1rem;
167
+ overflow: hidden;
168
+ }
169
+
170
+ .demo-child {
171
+ user-select: none;
172
+ position: relative;
173
+ margin: 0.125rem 0.5rem;
174
+ padding: 0.35rem 0.75rem;
175
+ transition: all 0.15s ease;
176
+ display: grid;
177
+ grid-template-columns: 16px 1fr;
178
+ align-items: center;
179
+ gap: 0.5rem;
180
+ border-radius: var(--radius);
181
+ cursor: pointer;
182
+ font-size: 0.7rem;
183
+ color: #777;
184
+ background: transparent;
185
+ }
186
+
187
+ .demo-child:hover {
188
+ background: rgba(59, 130, 246, 0.05);
189
+ color: #bbb;
190
+ }
191
+
192
+ .demo-child.selected {
193
+ background: rgba(59, 130, 246, 0.15);
194
+ color: var(--primary);
195
+ }
196
+
197
+ .demo-child .material-symbols-outlined {
198
+ font-size: 14px;
199
+ }
200
+
146
201
  ::-webkit-scrollbar {
147
202
  width: 8px;
148
203
  }
@@ -171,7 +226,7 @@ export class WccSidebar extends DeesElement {
171
226
  class="selectOption ${this.selectedItem === item ? 'selected' : null}"
172
227
  @click=${async () => {
173
228
  const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
174
- this.selectItem('page', pageName, item);
229
+ this.selectItem('page', pageName, item, 0);
175
230
  }}
176
231
  >
177
232
  <i class="material-symbols-outlined">insert_drive_file</i>
@@ -184,31 +239,83 @@ export class WccSidebar extends DeesElement {
184
239
  ${(() => {
185
240
  const elements = Object.keys(this.dashboardRef.elements);
186
241
  return elements.map(elementName => {
187
- const item = this.dashboardRef.elements[elementName];
188
- return html`
189
- <div
190
- class="selectOption ${this.selectedItem === item ? 'selected' : null}"
191
- @click=${async () => {
192
- const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
193
- this.selectItem('element', elementName, item);
194
- }}
195
- >
196
- <i class="material-symbols-outlined">featured_video</i>
197
- <div class="text">${elementName}</div>
198
- </div>
199
- `;
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
+ }
200
295
  });
201
296
  })()}
202
297
  </div>
203
298
  `;
204
299
  }
205
300
 
206
- public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement) {
301
+ private toggleExpanded(elementName: string) {
302
+ const newSet = new Set(this.expandedElements);
303
+ if (newSet.has(elementName)) {
304
+ newSet.delete(elementName);
305
+ } else {
306
+ newSet.add(elementName);
307
+ }
308
+ this.expandedElements = newSet;
309
+ }
310
+
311
+ public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement, demoIndex: number = 0) {
207
312
  console.log('selected item');
208
313
  console.log(itemNameArg);
209
314
  console.log(itemArg);
315
+ console.log('demo index:', demoIndex);
210
316
  this.selectedItem = itemArg;
211
317
  this.selectedType = typeArg;
318
+ this.dashboardRef.selectedDemoIndex = demoIndex;
212
319
  this.dispatchEvent(
213
320
  new CustomEvent('selectedType', {
214
321
  detail: typeArg
@@ -224,7 +331,11 @@ export class WccSidebar extends DeesElement {
224
331
  detail: itemArg
225
332
  })
226
333
  );
227
-
334
+
228
335
  this.dashboardRef.buildUrl();
336
+
337
+ // Force re-render to update demo child selection indicator
338
+ // (needed when switching between demos of the same element)
339
+ this.requestUpdate();
229
340
  }
230
341
  }
@@ -2,8 +2,39 @@ import type { TemplateResult } from 'lit';
2
2
 
3
3
  export type TTemplateFactory = () => TemplateResult | Promise<TemplateResult>;
4
4
 
5
+ // Demo can be a single function or an array of functions
6
+ export type TDemoDefinition = TTemplateFactory | TTemplateFactory[];
7
+
5
8
  export const resolveTemplateFactory = async (
6
9
  factoryArg: TTemplateFactory
7
10
  ): Promise<TemplateResult> => {
8
11
  return await Promise.resolve(factoryArg());
9
12
  };
13
+
14
+ /**
15
+ * Get the number of demos for an element
16
+ */
17
+ export const getDemoCount = (demo: TDemoDefinition): number => {
18
+ if (Array.isArray(demo)) {
19
+ return demo.length;
20
+ }
21
+ return 1;
22
+ };
23
+
24
+ /**
25
+ * Get a specific demo by index (0-based internally, displayed as 1-based)
26
+ */
27
+ export const getDemoAtIndex = (demo: TDemoDefinition, index: number): TTemplateFactory | null => {
28
+ if (Array.isArray(demo)) {
29
+ return demo[index] ?? null;
30
+ }
31
+ // Single demo - only index 0 is valid
32
+ return index === 0 ? demo : null;
33
+ };
34
+
35
+ /**
36
+ * Check if an element has multiple demos
37
+ */
38
+ export const hasMultipleDemos = (demo: TDemoDefinition): boolean => {
39
+ return Array.isArray(demo) && demo.length > 1;
40
+ };
package/ts_web/index.ts CHANGED
@@ -2,6 +2,11 @@ import { WccDashboard } from './elements/wcc-dashboard.js';
2
2
  import { LitElement } from 'lit';
3
3
  import type { TTemplateFactory } from './elements/wcctools.helpers.js';
4
4
 
5
+ // Export recording components and service
6
+ export { RecorderService, type IRecorderEvents, type IRecordingOptions } from './services/recorder.service.js';
7
+ export { WccRecordButton } from './elements/wcc-record-button.js';
8
+ export { WccRecordingPanel } from './elements/wcc-recording-panel.js';
9
+
5
10
  const setupWccTools = (
6
11
  elementsArg?: { [key: string]: LitElement },
7
12
  pagesArg?: Record<string, TTemplateFactory>
@@ -0,0 +1,123 @@
1
+ # @design.estate/dees-wcctools
2
+
3
+ 🛠️ **Web Component Catalogue Tools** — The core dashboard and UI components for building interactive component catalogues
4
+
5
+ ## Overview
6
+
7
+ This is the main module of `@design.estate/dees-wcctools`, providing the complete dashboard experience for developing, testing, and documenting web components.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pnpm add -D @design.estate/dees-wcctools
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { setupWccTools } from '@design.estate/dees-wcctools';
19
+ import { MyButton } from './components/my-button.js';
20
+
21
+ setupWccTools({
22
+ 'my-button': MyButton,
23
+ });
24
+ ```
25
+
26
+ ## Exports
27
+
28
+ ### Main Entry Point
29
+
30
+ | Export | Description |
31
+ |--------|-------------|
32
+ | `setupWccTools` | Initialize the component catalogue dashboard |
33
+
34
+ ### Recording Components
35
+
36
+ | Export | Description |
37
+ |--------|-------------|
38
+ | `RecorderService` | Service class for screen/viewport recording |
39
+ | `WccRecordButton` | Record button UI component |
40
+ | `WccRecordingPanel` | Recording options and preview panel |
41
+ | `IRecorderEvents` | TypeScript interface for recorder callbacks |
42
+ | `IRecordingOptions` | TypeScript interface for recording options |
43
+
44
+ ## Internal Components
45
+
46
+ The module includes these internal web components:
47
+
48
+ | Component | Description |
49
+ |-----------|-------------|
50
+ | `wcc-dashboard` | Main dashboard container with routing |
51
+ | `wcc-sidebar` | Navigation sidebar with element/page listing |
52
+ | `wcc-frame` | Iframe viewport with responsive sizing |
53
+ | `wcc-properties` | Property panel with live editing |
54
+ | `wcc-record-button` | Recording state indicator button |
55
+ | `wcc-recording-panel` | Recording workflow UI |
56
+
57
+ ## RecorderService API
58
+
59
+ For programmatic recording control:
60
+
61
+ ```typescript
62
+ import { RecorderService, type IRecorderEvents } from '@design.estate/dees-wcctools';
63
+
64
+ const events: IRecorderEvents = {
65
+ onDurationUpdate: (duration) => console.log(`Recording: ${duration}s`),
66
+ onRecordingComplete: (blob) => saveBlob(blob),
67
+ onAudioLevelUpdate: (level) => updateMeter(level),
68
+ onError: (error) => console.error(error),
69
+ onStreamEnded: () => console.log('User stopped sharing'),
70
+ };
71
+
72
+ const recorder = new RecorderService(events);
73
+
74
+ // Load available microphones
75
+ const mics = await recorder.loadMicrophones(true); // true = request permission
76
+
77
+ // Start audio level monitoring
78
+ await recorder.startAudioMonitoring(mics[0].deviceId);
79
+
80
+ // Start recording
81
+ await recorder.startRecording({
82
+ mode: 'viewport', // or 'screen'
83
+ audioDeviceId: mics[0].deviceId,
84
+ viewportElement: document.querySelector('.viewport'),
85
+ });
86
+
87
+ // Stop recording
88
+ recorder.stopRecording();
89
+
90
+ // Export trimmed video
91
+ const trimmedBlob = await recorder.exportTrimmedVideo(videoElement, startTime, endTime);
92
+
93
+ // Cleanup
94
+ recorder.dispose();
95
+ ```
96
+
97
+ ## Architecture
98
+
99
+ ```
100
+ ts_web/
101
+ ├── index.ts # Main exports
102
+ ├── elements/
103
+ │ ├── wcc-dashboard.ts # Root dashboard component
104
+ │ ├── wcc-sidebar.ts # Navigation sidebar
105
+ │ ├── wcc-frame.ts # Responsive iframe viewport
106
+ │ ├── wcc-properties.ts # Property editing panel
107
+ │ ├── wcc-record-button.ts # Recording button
108
+ │ ├── wcc-recording-panel.ts # Recording options/preview
109
+ │ └── wcctools.helpers.ts # Shared utilities
110
+ ├── services/
111
+ │ └── recorder.service.ts # MediaRecorder abstraction
112
+ └── pages/
113
+ └── index.ts # Built-in pages
114
+ ```
115
+
116
+ ## Features
117
+
118
+ - 🎨 Interactive component preview
119
+ - 🔧 Real-time property editing with type detection
120
+ - 🌓 Theme switching (light/dark)
121
+ - 📱 Responsive viewport testing
122
+ - 🎬 Screen recording with trimming
123
+ - 🔗 URL-based deep linking