@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,5 +1,5 @@
1
1
  import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
2
- import { resolveTemplateFactory } from './wcctools.helpers.js';
2
+ import { resolveTemplateFactory, getDemoAtIndex, getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
3
3
  import type { TTemplateFactory } from './wcctools.helpers.js';
4
4
 
5
5
  import * as plugins from '../wcctools.plugins.js';
@@ -17,38 +17,41 @@ import { WccFrame } from './wcc-frame.js';
17
17
  export class WccDashboard extends DeesElement {
18
18
 
19
19
  @property()
20
- public selectedType: TElementType;
20
+ accessor selectedType: TElementType;
21
21
 
22
22
  @property()
23
- public selectedItemName: string;
23
+ accessor selectedItemName: string;
24
24
 
25
25
  @property()
26
- public selectedItem: TTemplateFactory | DeesElement;
26
+ accessor selectedItem: TTemplateFactory | DeesElement;
27
+
28
+ @property({ type: Number })
29
+ accessor selectedDemoIndex: number = 0;
27
30
 
28
31
  @property()
29
- public selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
32
+ accessor selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
30
33
 
31
34
  @property()
32
- public selectedTheme: TTheme = 'dark';
35
+ accessor selectedTheme: TTheme = 'dark';
33
36
 
34
37
  @property()
35
- public isFullscreen: boolean = false;
38
+ accessor isFullscreen: boolean = false;
36
39
 
37
40
  @property()
38
- public pages: Record<string, TTemplateFactory> = {};
41
+ accessor pages: Record<string, TTemplateFactory> = {};
39
42
 
40
43
  @property()
41
- public elements: { [key: string]: DeesElement } = {};
44
+ accessor elements: { [key: string]: DeesElement } = {};
42
45
 
43
46
  @property()
44
- public warning: string = null;
47
+ accessor warning: string = null;
45
48
 
46
49
  private frameScrollY: number = 0;
47
50
  private sidebarScrollY: number = 0;
48
51
  private scrollPositionsApplied: boolean = false;
49
52
 
50
53
  @queryAsync('wcc-frame')
51
- public wccFrame: Promise<WccFrame>;
54
+ accessor wccFrame: Promise<WccFrame>;
52
55
 
53
56
  constructor(
54
57
  elementsArg?: { [key: string]: DeesElement },
@@ -151,11 +154,53 @@ export class WccDashboard extends DeesElement {
151
154
  this.setupScrollListeners();
152
155
  }, 500);
153
156
 
157
+ // Route with demo index (new format)
158
+ this.domtools.router.on(
159
+ '/wcctools-route/:itemType/:itemName/:demoIndex/:viewport/:theme',
160
+ async (routeInfo) => {
161
+ this.selectedType = routeInfo.params.itemType as TElementType;
162
+ this.selectedItemName = routeInfo.params.itemName;
163
+ this.selectedDemoIndex = parseInt(routeInfo.params.demoIndex) || 0;
164
+ this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
165
+ this.selectedTheme = routeInfo.params.theme as TTheme;
166
+ if (routeInfo.params.itemType === 'element') {
167
+ this.selectedItem = this.elements[routeInfo.params.itemName];
168
+ } else if (routeInfo.params.itemType === 'page') {
169
+ this.selectedItem = this.pages[routeInfo.params.itemName];
170
+ }
171
+
172
+ // Restore scroll positions from query parameters
173
+ if (routeInfo.queryParams) {
174
+ const frameScrollY = routeInfo.queryParams.frameScrollY;
175
+ const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
176
+
177
+ if (frameScrollY) {
178
+ this.frameScrollY = parseInt(frameScrollY);
179
+ }
180
+ if (sidebarScrollY) {
181
+ this.sidebarScrollY = parseInt(sidebarScrollY);
182
+ }
183
+
184
+ // Apply scroll positions after a short delay to ensure DOM is ready
185
+ setTimeout(() => {
186
+ this.applyScrollPositions();
187
+ }, 100);
188
+ }
189
+
190
+ const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
191
+ this.selectedTheme === 'bright'
192
+ ? domtoolsInstance.themeManager.goBright()
193
+ : domtoolsInstance.themeManager.goDark();
194
+ }
195
+ );
196
+
197
+ // Legacy route without demo index (for backwards compatibility)
154
198
  this.domtools.router.on(
155
199
  '/wcctools-route/:itemType/:itemName/:viewport/:theme',
156
200
  async (routeInfo) => {
157
201
  this.selectedType = routeInfo.params.itemType as TElementType;
158
202
  this.selectedItemName = routeInfo.params.itemName;
203
+ this.selectedDemoIndex = 0; // Default to first demo
159
204
  this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
160
205
  this.selectedTheme = routeInfo.params.theme as TTheme;
161
206
  if (routeInfo.params.itemType === 'element') {
@@ -163,25 +208,25 @@ export class WccDashboard extends DeesElement {
163
208
  } else if (routeInfo.params.itemType === 'page') {
164
209
  this.selectedItem = this.pages[routeInfo.params.itemName];
165
210
  }
166
-
211
+
167
212
  // Restore scroll positions from query parameters
168
213
  if (routeInfo.queryParams) {
169
214
  const frameScrollY = routeInfo.queryParams.frameScrollY;
170
215
  const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
171
-
216
+
172
217
  if (frameScrollY) {
173
218
  this.frameScrollY = parseInt(frameScrollY);
174
219
  }
175
220
  if (sidebarScrollY) {
176
221
  this.sidebarScrollY = parseInt(sidebarScrollY);
177
222
  }
178
-
223
+
179
224
  // Apply scroll positions after a short delay to ensure DOM is ready
180
225
  setTimeout(() => {
181
226
  this.applyScrollPositions();
182
227
  }, 100);
183
228
  }
184
-
229
+
185
230
  const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
186
231
  this.selectedTheme === 'bright'
187
232
  ? domtoolsInstance.themeManager.goBright()
@@ -218,33 +263,48 @@ export class WccDashboard extends DeesElement {
218
263
  this.setWarning(`component ${anonItem.name} does not expose a demo property.`);
219
264
  return;
220
265
  }
221
- if (!(typeof anonItem.demo === 'function')) {
266
+
267
+ // Support both single demo (function) and multiple demos (array)
268
+ const isArray = Array.isArray(anonItem.demo);
269
+ const isFunction = typeof anonItem.demo === 'function';
270
+
271
+ if (!isArray && !isFunction) {
222
272
  this.setWarning(
223
- `component ${anonItem.name} has demo property, but it is not of type function`
273
+ `component ${anonItem.name} has demo property, but it is not a function or array of functions`
224
274
  );
225
275
  return;
226
276
  }
277
+
278
+ // Get the specific demo to render
279
+ const demoFactory = getDemoAtIndex(anonItem.demo, this.selectedDemoIndex);
280
+ if (!demoFactory) {
281
+ this.setWarning(
282
+ `component ${anonItem.name} does not have a demo at index ${this.selectedDemoIndex + 1}`
283
+ );
284
+ return;
285
+ }
286
+
227
287
  this.setWarning(null);
228
288
  const viewport = await wccFrame.getViewportElement();
229
- const demoTemplate = await resolveTemplateFactory(() => anonItem.demo());
289
+ const demoTemplate = await resolveTemplateFactory(demoFactory);
230
290
  render(demoTemplate, viewport);
231
291
  }
232
292
  }
233
293
 
234
294
  public buildUrl() {
235
- const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
295
+ const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
236
296
  const queryParams = new URLSearchParams();
237
-
297
+
238
298
  if (this.frameScrollY > 0) {
239
299
  queryParams.set('frameScrollY', this.frameScrollY.toString());
240
300
  }
241
301
  if (this.sidebarScrollY > 0) {
242
302
  queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
243
303
  }
244
-
304
+
245
305
  const queryString = queryParams.toString();
246
306
  const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
247
-
307
+
248
308
  this.domtools.router.pushUrl(fullUrl);
249
309
  }
250
310
 
@@ -286,19 +346,19 @@ export class WccDashboard extends DeesElement {
286
346
  }
287
347
 
288
348
  private updateUrlWithScrollState() {
289
- const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
349
+ const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
290
350
  const queryParams = new URLSearchParams();
291
-
351
+
292
352
  if (this.frameScrollY > 0) {
293
353
  queryParams.set('frameScrollY', this.frameScrollY.toString());
294
354
  }
295
355
  if (this.sidebarScrollY > 0) {
296
356
  queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
297
357
  }
298
-
358
+
299
359
  const queryString = queryParams.toString();
300
360
  const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
301
-
361
+
302
362
  // Use replaceState to update URL without navigation
303
363
  window.history.replaceState(null, '', fullUrl);
304
364
  }
@@ -11,13 +11,13 @@ declare global {
11
11
  @customElement('wcc-frame')
12
12
  export class WccFrame extends DeesElement {
13
13
  @property()
14
- public viewport: string;
14
+ accessor viewport: string;
15
15
 
16
16
  @property({ type: Boolean })
17
- public advancedEditorOpen: boolean = false;
17
+ accessor advancedEditorOpen: boolean = false;
18
18
 
19
19
  @property({ type: Boolean })
20
- public isFullscreen: boolean = false;
20
+ accessor isFullscreen: boolean = false;
21
21
 
22
22
  public static styles = [
23
23
  css`
@@ -1,6 +1,8 @@
1
1
  import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
2
2
  import { WccDashboard } from './wcc-dashboard.js';
3
3
  import type { TTemplateFactory } from './wcctools.helpers.js';
4
+ import './wcc-record-button.js';
5
+ import './wcc-recording-panel.js';
4
6
 
5
7
  export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array';
6
8
 
@@ -18,28 +20,28 @@ export class WccProperties extends DeesElement {
18
20
  @property({
19
21
  type: WccDashboard
20
22
  })
21
- public dashboardRef: WccDashboard;
23
+ accessor dashboardRef: WccDashboard;
22
24
 
23
25
  @property()
24
- public selectedItem: TTemplateFactory | DeesElement;
26
+ accessor selectedItem: TTemplateFactory | DeesElement;
25
27
 
26
28
  @property()
27
- public selectedViewport: TEnvironment = 'native';
29
+ accessor selectedViewport: TEnvironment = 'native';
28
30
 
29
31
  @property()
30
- public selectedTheme: TTheme = 'dark';
32
+ accessor selectedTheme: TTheme = 'dark';
31
33
 
32
34
  @property()
33
- public warning: string = null;
35
+ accessor warning: string = null;
34
36
 
35
37
  @property()
36
- public isFullscreen: boolean = false;
38
+ accessor isFullscreen: boolean = false;
37
39
 
38
40
  @state()
39
- propertyContent: TemplateResult[] = [];
41
+ accessor propertyContent: TemplateResult[] = [];
40
42
 
41
43
  @state()
42
- editingProperties: Array<{
44
+ accessor editingProperties: Array<{
43
45
  id: string;
44
46
  name: string;
45
47
  value: any;
@@ -48,6 +50,16 @@ export class WccProperties extends DeesElement {
48
50
  editorError: string;
49
51
  }> = [];
50
52
 
53
+ // Recording coordination state
54
+ @state()
55
+ accessor showRecordingPanel: boolean = false;
56
+
57
+ @state()
58
+ accessor isRecording: boolean = false;
59
+
60
+ @state()
61
+ accessor recordingDuration: number = 0;
62
+
51
63
  public editorHeight: number = 300;
52
64
 
53
65
  public render(): TemplateResult {
@@ -88,7 +100,7 @@ export class WccProperties extends DeesElement {
88
100
  }
89
101
  .grid {
90
102
  display: grid;
91
- grid-template-columns: 1fr 150px 300px 70px;
103
+ grid-template-columns: 1fr 150px 300px 70px 70px;
92
104
  height: 100%;
93
105
  }
94
106
  .properties {
@@ -654,9 +666,26 @@ export class WccProperties extends DeesElement {
654
666
  ${this.isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
655
667
  </i>
656
668
  </div>
669
+ <!-- Recording Button -->
670
+ <wcc-record-button
671
+ .state=${this.isRecording ? 'recording' : 'idle'}
672
+ .duration=${this.recordingDuration}
673
+ @record-click=${() => this.handleRecordButtonClick()}
674
+ ></wcc-record-button>
657
675
  </div>
658
676
  ${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
659
677
  </div>
678
+
679
+ <!-- Recording Panel (options + preview) -->
680
+ ${this.showRecordingPanel ? html`
681
+ <wcc-recording-panel
682
+ .dashboardRef=${this.dashboardRef}
683
+ @recording-start=${() => { this.isRecording = true; }}
684
+ @recording-stop=${() => { this.isRecording = false; }}
685
+ @duration-update=${(e: CustomEvent) => { this.recordingDuration = e.detail.duration; }}
686
+ @close=${() => { this.showRecordingPanel = false; this.isRecording = false; this.recordingDuration = 0; }}
687
+ ></wcc-recording-panel>
688
+ ` : null}
660
689
  `;
661
690
  }
662
691
 
@@ -994,4 +1023,19 @@ export class WccProperties extends DeesElement {
994
1023
  })
995
1024
  );
996
1025
  }
1026
+
1027
+ // ==================== Recording Methods ====================
1028
+
1029
+ private handleRecordButtonClick() {
1030
+ if (this.isRecording) {
1031
+ // Stop recording by calling the panel's stopRecording method
1032
+ const panel = this.shadowRoot?.querySelector('wcc-recording-panel') as any;
1033
+ if (panel && panel.stopRecording) {
1034
+ panel.stopRecording();
1035
+ }
1036
+ } else {
1037
+ // Toggle the recording panel
1038
+ this.showRecordingPanel = !this.showRecordingPanel;
1039
+ }
1040
+ }
997
1041
  }
@@ -0,0 +1,108 @@
1
+ import { DeesElement, customElement, html, css, property, type TemplateResult } from '@design.estate/dees-element';
2
+
3
+ @customElement('wcc-record-button')
4
+ export class WccRecordButton extends DeesElement {
5
+ @property({ type: String })
6
+ accessor state: 'idle' | 'recording' = 'idle';
7
+
8
+ @property({ type: Number })
9
+ accessor duration: number = 0;
10
+
11
+ public static styles = [
12
+ css`
13
+ :host {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ background: transparent;
18
+ cursor: pointer;
19
+ transition: all 0.15s ease;
20
+ color: #666;
21
+ user-select: none;
22
+ }
23
+
24
+ :host(:hover) {
25
+ background: rgba(239, 68, 68, 0.05);
26
+ color: #f87171;
27
+ }
28
+
29
+ :host(.recording) {
30
+ background: rgba(239, 68, 68, 0.15);
31
+ color: #f87171;
32
+ }
33
+
34
+ .content {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ gap: 0.25rem;
39
+ }
40
+
41
+ .rec-icon {
42
+ width: 12px;
43
+ height: 12px;
44
+ border-radius: 50%;
45
+ background: currentColor;
46
+ }
47
+
48
+ :host(.recording) .rec-icon {
49
+ animation: pulse-recording 1s ease-in-out infinite;
50
+ }
51
+
52
+ @keyframes pulse-recording {
53
+ 0%, 100% { opacity: 1; transform: scale(1); }
54
+ 50% { opacity: 0.5; transform: scale(0.9); }
55
+ }
56
+
57
+ .recording-timer {
58
+ font-family: 'Consolas', 'Monaco', monospace;
59
+ font-size: 0.7rem;
60
+ }
61
+ `
62
+ ];
63
+
64
+ private formatDuration(seconds: number): string {
65
+ const mins = Math.floor(seconds / 60);
66
+ const secs = seconds % 60;
67
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
68
+ }
69
+
70
+ public render(): TemplateResult {
71
+ return html`
72
+ <div class="content">
73
+ <div class="rec-icon"></div>
74
+ ${this.state === 'recording' ? html`
75
+ <span class="recording-timer">${this.formatDuration(this.duration)}</span>
76
+ ` : null}
77
+ </div>
78
+ `;
79
+ }
80
+
81
+ async connectedCallback(): Promise<void> {
82
+ await super.connectedCallback();
83
+ this.addEventListener('click', this.handleClick);
84
+ }
85
+
86
+ async disconnectedCallback(): Promise<void> {
87
+ await super.disconnectedCallback();
88
+ this.removeEventListener('click', this.handleClick);
89
+ }
90
+
91
+ private handleClick = (): void => {
92
+ this.dispatchEvent(new CustomEvent('record-click', {
93
+ bubbles: true,
94
+ composed: true
95
+ }));
96
+ };
97
+
98
+ updated(changedProperties: Map<string, unknown>): void {
99
+ super.updated(changedProperties);
100
+ if (changedProperties.has('state')) {
101
+ if (this.state === 'recording') {
102
+ this.classList.add('recording');
103
+ } else {
104
+ this.classList.remove('recording');
105
+ }
106
+ }
107
+ }
108
+ }